diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c index 2a44b6e..d712a94 100644 --- a/src/backend/utils/adt/datetime.c +++ b/src/backend/utils/adt/datetime.c @@ -43,8 +43,12 @@ static int DecodeTime(char *str, int fmask, int range, static const datetkn *datebsearch(const char *key, const datetkn *base, int nel); static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits, struct pg_tm * tm); -static void TrimTrailingZeros(char *str); -static void AppendSeconds(char *cp, int sec, fsec_t fsec, + +#ifndef HAVE_INT64_TIMESTAMP +static char *TrimTrailingZeros(char *str); +#endif /* HAVE_INT64_TIMESTAMP */ + +static char *AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros); static void AdjustFractSeconds(double frac, struct pg_tm * tm, fsec_t *fsec, int scale); @@ -394,15 +398,16 @@ GetCurrentTimeUsec(struct pg_tm * tm, fsec_t *fsec, int *tzp) *tzp = tz; } - +#ifndef HAVE_INT64_TIMESTAMP /* TrimTrailingZeros() * ... resulting from printing numbers with full precision. * * Before Postgres 8.4, this always left at least 2 fractional digits, * but conversations on the lists suggest this isn't desired * since showing '0.10' is misleading with values of precision(1). + * Returns a pointer to the new end of string. */ -static void +static char * TrimTrailingZeros(char *str) { int len = strlen(str); @@ -412,43 +417,98 @@ TrimTrailingZeros(char *str) len--; *(str + len) = '\0'; } + + return str + len; } +#endif /* HAVE_INT64_TIMESTAMP */ /* - * Append sections and fractional seconds (if any) at *cp. + * Append seconds and fractional seconds (if any) at *cp. * precision is the max number of fraction digits, fillzeros says to * pad to two integral-seconds digits. * Note that any sign is stripped from the input seconds values. + * Note 'precision' must not be a negative number. */ -static void +static char * AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros) { +#ifdef HAVE_INT64_TIMESTAMP + + if (fillzeros) + cp = pg_int2str_zeropad(cp, abs(sec), 2); + else + cp = pg_int2str(cp, abs(sec)); + + if (fsec != 0) + { + int value = (int) Abs(fsec); + char *end = &cp[precision + 1]; + bool gotnonzero = false; + + *cp++ = '.'; + + /* + * Append the fractional seconds part. Note that we don't want any + * trailing zeros here, so since we're building the number in reverse + * we'll skip appending any zeros, unless we've seen a non-zero. + */ + while (precision--) + { + int remainder; + int oldval = value; + + value /= 10; + remainder = oldval - value * 10; + + /* check if we got a non-zero */ + if (remainder) + gotnonzero = true; + + if (gotnonzero) + cp[precision] = '0' + remainder; + else + end = &cp[precision]; + } + + /* + * If we have a non-zero value then precision must have not been enough + * to print the number, we'd better have another go. There won't be any + * zero padding, so we can just use pg_int2str(). + */ + if (value > 0) + return pg_int2str(cp, fsec); + + *end = '\0'; + + return end; + } + else + return cp; +#else + if (fsec == 0) { if (fillzeros) - sprintf(cp, "%02d", abs(sec)); + return pg_int2str_zeropad(cp, abs(sec), 2); else - sprintf(cp, "%d", abs(sec)); + return pg_int2str(cp, abs(sec)); } else { -#ifdef HAVE_INT64_TIMESTAMP - if (fillzeros) - sprintf(cp, "%02d.%0*d", abs(sec), precision, (int) Abs(fsec)); - else - sprintf(cp, "%d.%0*d", abs(sec), precision, (int) Abs(fsec)); -#else if (fillzeros) sprintf(cp, "%0*.*f", precision + 3, precision, fabs(sec + fsec)); else sprintf(cp, "%.*f", precision, fabs(sec + fsec)); -#endif - TrimTrailingZeros(cp); + + return TrimTrailingZeros(cp); } + +#endif /* HAVE_INT64_TIMESTAMP */ } + /* Variant of above that's specialized to timestamp case */ -static void +static char * AppendTimestampSeconds(char *cp, struct pg_tm * tm, fsec_t fsec) { /* @@ -459,7 +519,7 @@ AppendTimestampSeconds(char *cp, struct pg_tm * tm, fsec_t fsec) if (tm->tm_year <= 0) fsec = 0; #endif - AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true); + return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true); } /* @@ -3831,9 +3891,11 @@ datebsearch(const char *key, const datetkn *base, int nel) } /* EncodeTimezone() - * Append representation of a numeric timezone offset to str. + * Copies representation of a numeric timezone offset into str. + * Returns a pointer pointing to the NUL character at the end of the + * string. */ -static void +static char * EncodeTimezone(char *str, int tz, int style) { int hour, @@ -3846,16 +3908,26 @@ EncodeTimezone(char *str, int tz, int style) hour = min / MINS_PER_HOUR; min -= hour * MINS_PER_HOUR; - str += strlen(str); /* TZ is negated compared to sign we wish to display ... */ *str++ = (tz <= 0 ? '+' : '-'); if (sec != 0) - sprintf(str, "%02d:%02d:%02d", hour, min, sec); + { + str = pg_int2str_zeropad(str, hour, 2); + *str++ = ':'; + str = pg_int2str_zeropad(str, min, 2); + *str++ = ':'; + str = pg_int2str_zeropad(str, sec, 2); + } else if (min != 0 || style == USE_XSD_DATES) - sprintf(str, "%02d:%02d", hour, min); + { + str = pg_int2str_zeropad(str, hour, 2); + *str++ = ':'; + str = pg_int2str_zeropad(str, min, 2); + } else - sprintf(str, "%02d", hour); + str = pg_int2str_zeropad(str, hour, 2); + return str; } /* EncodeDateOnly() @@ -3871,46 +3943,74 @@ EncodeDateOnly(struct pg_tm * tm, int style, char *str) case USE_ISO_DATES: case USE_XSD_DATES: /* compatible with ISO date formats */ - if (tm->tm_year > 0) - sprintf(str, "%04d-%02d-%02d", - tm->tm_year, tm->tm_mon, tm->tm_mday); - else - sprintf(str, "%04d-%02d-%02d %s", - -(tm->tm_year - 1), tm->tm_mon, tm->tm_mday, "BC"); + str = pg_int2str_zeropad(str, + (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4); + *str++ = '-'; + str = pg_int2str_zeropad(str, tm->tm_mon, 2); + *str++ = '-'; + str = pg_int2str_zeropad(str, tm->tm_mday, 2); + + if (tm->tm_year <= 0) + memcpy(str, " BC", 4); break; case USE_SQL_DATES: /* compatible with Oracle/Ingres date formats */ if (DateOrder == DATEORDER_DMY) - sprintf(str, "%02d/%02d", tm->tm_mday, tm->tm_mon); - else - sprintf(str, "%02d/%02d", tm->tm_mon, tm->tm_mday); - if (tm->tm_year > 0) - sprintf(str + 5, "/%04d", tm->tm_year); + { + str = pg_int2str_zeropad(str, tm->tm_mday, 2); + *str++ = '/'; + str = pg_int2str_zeropad(str, tm->tm_mon, 2); + } else - sprintf(str + 5, "/%04d %s", -(tm->tm_year - 1), "BC"); + { + str = pg_int2str_zeropad(str, tm->tm_mon, 2); + *str++ = '/'; + str = pg_int2str_zeropad(str, tm->tm_mday, 2); + } + + *str++ = '/'; + str = pg_int2str_zeropad(str, + (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4); + + if (tm->tm_year <= 0) + memcpy(str, " BC", 4); break; case USE_GERMAN_DATES: /* German-style date format */ - sprintf(str, "%02d.%02d", tm->tm_mday, tm->tm_mon); - if (tm->tm_year > 0) - sprintf(str + 5, ".%04d", tm->tm_year); - else - sprintf(str + 5, ".%04d %s", -(tm->tm_year - 1), "BC"); + str = pg_int2str_zeropad(str, tm->tm_mday, 2); + *str++ = '.'; + str = pg_int2str_zeropad(str, tm->tm_mon, 2); + *str++ = '.'; + str = pg_int2str_zeropad(str, + (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4); + + if (tm->tm_year <= 0) + memcpy(str, " BC", 4); break; case USE_POSTGRES_DATES: default: /* traditional date-only style for Postgres */ if (DateOrder == DATEORDER_DMY) - sprintf(str, "%02d-%02d", tm->tm_mday, tm->tm_mon); - else - sprintf(str, "%02d-%02d", tm->tm_mon, tm->tm_mday); - if (tm->tm_year > 0) - sprintf(str + 5, "-%04d", tm->tm_year); + { + str = pg_int2str_zeropad(str, tm->tm_mday, 2); + *str++ = '-'; + str = pg_int2str_zeropad(str, tm->tm_mon, 2); + } else - sprintf(str + 5, "-%04d %s", -(tm->tm_year - 1), "BC"); + { + str = pg_int2str_zeropad(str, tm->tm_mon, 2); + *str++ = '-'; + str = pg_int2str_zeropad(str, tm->tm_mday, 2); + } + *str++ = '-'; + str = pg_int2str_zeropad(str, + (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4); + + if (tm->tm_year <= 0) + memcpy(str, " BC", 4); break; } } @@ -3927,10 +4027,11 @@ EncodeDateOnly(struct pg_tm * tm, int style, char *str) void EncodeTimeOnly(struct pg_tm * tm, fsec_t fsec, bool print_tz, int tz, int style, char *str) { - sprintf(str, "%02d:%02d:", tm->tm_hour, tm->tm_min); - str += strlen(str); - - AppendSeconds(str, tm->tm_sec, fsec, MAX_TIME_PRECISION, true); + str = pg_int2str_zeropad(str, tm->tm_hour, 2); + *str++ = ':'; + str = pg_int2str_zeropad(str, tm->tm_min, 2); + *str++ = ':'; + str = AppendSeconds(str, tm->tm_sec, fsec, MAX_TIME_PRECISION, true); if (print_tz) EncodeTimezone(str, tz, style); @@ -3971,38 +4072,52 @@ EncodeDateTime(struct pg_tm * tm, fsec_t fsec, bool print_tz, int tz, const char case USE_ISO_DATES: case USE_XSD_DATES: /* Compatible with ISO-8601 date formats */ - - if (style == USE_ISO_DATES) - sprintf(str, "%04d-%02d-%02d %02d:%02d:", - (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), - tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min); - else - sprintf(str, "%04d-%02d-%02dT%02d:%02d:", - (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), - tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min); - - AppendTimestampSeconds(str + strlen(str), tm, fsec); + str = pg_int2str_zeropad(str, + (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4); + *str++ = '-'; + str = pg_int2str_zeropad(str, tm->tm_mon, 2); + *str++ = '-'; + str = pg_int2str_zeropad(str, tm->tm_mday, 2); + *str++ = (style == USE_ISO_DATES) ? ' ' : 'T'; + str = pg_int2str_zeropad(str, tm->tm_hour, 2); + *str++ = ':'; + str = pg_int2str_zeropad(str, tm->tm_min, 2); + *str++ = ':'; + + str = AppendTimestampSeconds(str, tm, fsec); if (print_tz) - EncodeTimezone(str, tz, style); + str = EncodeTimezone(str, tz, style); if (tm->tm_year <= 0) - sprintf(str + strlen(str), " BC"); + memcpy(str, " BC", 4); break; case USE_SQL_DATES: /* Compatible with Oracle/Ingres date formats */ if (DateOrder == DATEORDER_DMY) - sprintf(str, "%02d/%02d", tm->tm_mday, tm->tm_mon); + { + str = pg_int2str_zeropad(str, tm->tm_mday, 2); + *str++ = '/'; + str = pg_int2str_zeropad(str, tm->tm_mon, 2); + } else - sprintf(str, "%02d/%02d", tm->tm_mon, tm->tm_mday); - - sprintf(str + 5, "/%04d %02d:%02d:", - (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), - tm->tm_hour, tm->tm_min); + { + str = pg_int2str_zeropad(str, tm->tm_mon, 2); + *str++ = '/'; + str = pg_int2str_zeropad(str, tm->tm_mday, 2); + } - AppendTimestampSeconds(str + strlen(str), tm, fsec); + *str++ = '/'; + str = pg_int2str_zeropad(str, + (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4); + *str++ = ' '; + str = pg_int2str_zeropad(str, tm->tm_hour, 2); + *str++ = ':'; + str = pg_int2str_zeropad(str, tm->tm_min, 2); + *str++ = ':'; + str = AppendTimestampSeconds(str, tm, fsec); /* * Note: the uses of %.*s in this function would be risky if the @@ -4013,36 +4128,47 @@ EncodeDateTime(struct pg_tm * tm, fsec_t fsec, bool print_tz, int tz, const char if (print_tz) { if (tzn) - sprintf(str + strlen(str), " %.*s", MAXTZLEN, tzn); + { + sprintf(str, " %.*s", MAXTZLEN, tzn); + str += strlen(str); + } else - EncodeTimezone(str, tz, style); + str = EncodeTimezone(str, tz, style); } if (tm->tm_year <= 0) - sprintf(str + strlen(str), " BC"); + memcpy(str, " BC", 4); break; case USE_GERMAN_DATES: /* German variant on European style */ - sprintf(str, "%02d.%02d", tm->tm_mday, tm->tm_mon); - - sprintf(str + 5, ".%04d %02d:%02d:", - (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), - tm->tm_hour, tm->tm_min); - - AppendTimestampSeconds(str + strlen(str), tm, fsec); + str = pg_int2str_zeropad(str, tm->tm_mday, 2); + *str++ = '.'; + str = pg_int2str_zeropad(str, tm->tm_mon, 2); + *str++ = '.'; + str = pg_int2str_zeropad(str, + (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4); + *str++ = ' '; + str = pg_int2str_zeropad(str, tm->tm_hour, 2); + *str++ = ':'; + str = pg_int2str_zeropad(str, tm->tm_min, 2); + *str++ = ':'; + str = AppendTimestampSeconds(str, tm, fsec); if (print_tz) { if (tzn) - sprintf(str + strlen(str), " %.*s", MAXTZLEN, tzn); + { + sprintf(str, " %.*s", MAXTZLEN, tzn); + str += strlen(str); + } else - EncodeTimezone(str, tz, style); + str = EncodeTimezone(str, tz, style); } if (tm->tm_year <= 0) - sprintf(str + strlen(str), " BC"); + memcpy(str, " BC", 4); break; case USE_POSTGRES_DATES: @@ -4053,24 +4179,33 @@ EncodeDateTime(struct pg_tm * tm, fsec_t fsec, bool print_tz, int tz, const char tm->tm_wday = j2day(day); memcpy(str, days[tm->tm_wday], 3); - strcpy(str + 3, " "); + str += 3; + *str++ = ' '; if (DateOrder == DATEORDER_DMY) - sprintf(str + 4, "%02d %3s", tm->tm_mday, months[tm->tm_mon - 1]); + sprintf(str, "%02d %3s", tm->tm_mday, months[tm->tm_mon - 1]); else - sprintf(str + 4, "%3s %02d", months[tm->tm_mon - 1], tm->tm_mday); - - sprintf(str + 10, " %02d:%02d:", tm->tm_hour, tm->tm_min); + sprintf(str, "%3s %02d", months[tm->tm_mon - 1], tm->tm_mday); + str += 6; - AppendTimestampSeconds(str + strlen(str), tm, fsec); + *str++ = ' '; + str = pg_int2str_zeropad(str, tm->tm_hour, 2); + *str++ = ':'; + str = pg_int2str_zeropad(str, tm->tm_min, 2); + *str++ = ':'; + str = AppendTimestampSeconds(str, tm, fsec); - sprintf(str + strlen(str), " %04d", - (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1)); + *str++ = ' '; + str = pg_int2str_zeropad(str, + (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4); if (print_tz) { if (tzn) - sprintf(str + strlen(str), " %.*s", MAXTZLEN, tzn); + { + sprintf(str, " %.*s", MAXTZLEN, tzn); + str += strlen(str); + } else { /* @@ -4079,13 +4214,13 @@ EncodeDateTime(struct pg_tm * tm, fsec_t fsec, bool print_tz, int tz, const char * avoid formatting something which would be rejected by * the date/time parser later. - thomas 2001-10-19 */ - sprintf(str + strlen(str), " "); - EncodeTimezone(str, tz, style); + *str++ = ' '; + str = EncodeTimezone(str, tz, style); } } if (tm->tm_year <= 0) - sprintf(str + strlen(str), " BC"); + memcpy(str, " BC", 4); break; } } @@ -4284,8 +4419,7 @@ EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str) { if (sec < 0 || fsec < 0) *cp++ = '-'; - AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false); - cp += strlen(cp); + cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false); *cp++ = 'S'; *cp++ = '\0'; } @@ -4337,8 +4471,7 @@ EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str) } else if (is_before) *cp++ = '-'; - AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false); - cp += strlen(cp); + cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false); sprintf(cp, " sec%s", (abs(sec) != 1 || fsec != 0) ? "s" : ""); is_zero = FALSE; diff --git a/src/backend/utils/adt/numutils.c b/src/backend/utils/adt/numutils.c index 1dadbd5..cb51b8f 100644 --- a/src/backend/utils/adt/numutils.c +++ b/src/backend/utils/adt/numutils.c @@ -227,3 +227,147 @@ pg_lltoa(int64 value, char *a) *a-- = swap; } } + + +/* + * pg_int2str_zeropad + * Converts 'value' into a decimal string representation of the number. + * 'padding' specifies the minimum width of the number. Any extra space + * is filled up by prefixing the number with zeros. The return value is a + * pointer to the NUL terminated end of the string. + * + * Note: Callers should ensure that 'padding' is above zero. + * Note: This function is optimized for the case where the number is not too + * big to fit inside of the specified padding. + * Note: Caller must ensure that 'str' points to enough memory to hold the + result (at least 12 bytes, counting a leading sign and trailing NUL, + or padding + 1 bytes, whichever is larger). + */ +char * +pg_int2str_zeropad(char *str, int32 value, int32 padding) +{ + char *start = str; + char *end = &str[padding]; + int num = value; + + Assert(padding > 0); + + /* + * Handle negative numbers in a special way. We can't just append a '-' + * prefix and reverse the sign as on two's complement machines negative + * numbers can be 1 further from 0 than positive numbers, we do it this way + * so we properly handle the smallest possible value. + */ + if (num < 0) + { + *start++ = '-'; + padding--; + + /* + * Build the number starting at the end. Here remainder will be a + * negative number, we must reverse this sign on this before adding + * '0' in order to get the correct ASCII digit + */ + while (padding--) + { + int32 remainder; + int32 oldval = num; + + num /= 10; + remainder = oldval - num * 10; + start[padding] = '0' + -remainder; + } + } + else + { + /* build the number starting at the end */ + while (padding--) + { + int32 remainder; + int32 oldval = num; + + num /= 10; + remainder = oldval - num * 10; + start[padding] = '0' + remainder; + } + } + + /* + * If padding was not high enough to fit this number then num won't have + * been divided down to zero. We'd better have another go, this time we + * know there won't be any zero padding required so we can just enlist the + * help of pg_int2str() + */ + if (num != 0) + return pg_int2str(str, value); + + *end = '\0'; + return end; +} + +/* + * pg_int2str + * Converts 'value' into a decimal string representation of the number. + * + * Caller must ensure that 'str' points to enough memory to hold the result + * (at least 12 bytes, counting a leading sign and trailing NUL). + * Return value is a pointer to the new NUL terminated end of string. + */ +char * +pg_int2str(char *str, int32 value) +{ + char *start; + char *end; + + /* + * Handle negative numbers in a special way. We can't just append a '-' + * prefix and reverse the sign as on two's complement machines negative + * numbers can be 1 further from 0 than positive numbers, we do it this way + * so we properly handle the smallest possible value. + */ + if (value < 0) + { + *str++ = '-'; + + /* mark the position we must reverse the string from. */ + start = str; + + /* Compute the result string backwards. */ + do + { + int32 remainder; + int32 oldval = value; + + value /= 10; + remainder = oldval - value * 10; + *str++ = '0' + -remainder; + } while (value != 0); + } + else + { + /* mark the position we must reverse the string from. */ + start = str; + do + { + int32 remainder; + int32 oldval = value; + + value /= 10; + remainder = oldval - value * 10; + *str++ = '0' + remainder; + } while (value != 0); + } + + /* Add trailing NUL byte, and back up 'str' to the last character. */ + end = str; + *str-- = '\0'; + + /* Reverse string. */ + while (start < str) + { + char swap = *start; + *start++ = *str; + *str-- = swap; + } + return end; +} diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index fc1679e..a8644b7 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -289,6 +289,8 @@ extern int32 pg_atoi(const char *s, int size, int c); extern void pg_itoa(int16 i, char *a); extern void pg_ltoa(int32 l, char *a); extern void pg_lltoa(int64 ll, char *a); +extern char *pg_int2str_zeropad(char *str, int32 value, int32 padding); +extern char *pg_int2str(char *str, int32 value); /* * Per-opclass comparison functions for new btrees. These are diff --git a/src/tools/msvc/build.pl b/src/tools/msvc/build.pl index e107d41..1e2dd62 100644 --- a/src/tools/msvc/build.pl +++ b/src/tools/msvc/build.pl @@ -61,7 +61,7 @@ elsif ($buildwhat) } else { - system("msbuild pgsql.sln /verbosity:detailed /p:Configuration=$bconf"); + system("msbuild pgsql.sln /verbosity:quiet /p:Configuration=$bconf"); } # report status