From 7c5a561f31339ac3cfedb2dab066eca331dd75ac Mon Sep 17 00:00:00 2001 From: Michael Meskes Date: Wed, 26 Nov 2008 16:31:02 +0000 Subject: [PATCH] Applied patch by Ron Mayer to merge the new interval style into ecpg. --- src/interfaces/ecpg/ChangeLog | 2 + src/interfaces/ecpg/pgtypeslib/dt.h | 48 +- src/interfaces/ecpg/pgtypeslib/interval.c | 1064 +++++++++++------ .../ecpg/test/expected/pgtypeslib-dt_test.c | 22 +- .../test/expected/pgtypeslib-dt_test.stderr | 30 +- .../ecpg/test/pgtypeslib/dt_test.pgc | 1 + 6 files changed, 791 insertions(+), 376 deletions(-) diff --git a/src/interfaces/ecpg/ChangeLog b/src/interfaces/ecpg/ChangeLog index 939b2e5914..c6064cd0af 100644 --- a/src/interfaces/ecpg/ChangeLog +++ b/src/interfaces/ecpg/ChangeLog @@ -2394,6 +2394,8 @@ Sat, 25 Oct 2008 16:34:28 +0200 Wed, 26 Nov 2008 14:09:08 +0100 - When creating a varchar struct name braces must be discarded. + - Applied patch by Ron Mayer to merge + the new interval style into ecpg. - Set pgtypes library version to 3.1. - Set compat library version to 3.1. - Set ecpg library version to 6.2. diff --git a/src/interfaces/ecpg/pgtypeslib/dt.h b/src/interfaces/ecpg/pgtypeslib/dt.h index 47905dedfe..0008211fad 100644 --- a/src/interfaces/ecpg/pgtypeslib/dt.h +++ b/src/interfaces/ecpg/pgtypeslib/dt.h @@ -1,4 +1,4 @@ -/* $PostgreSQL: pgsql/src/interfaces/ecpg/pgtypeslib/dt.h,v 1.39 2007/11/15 21:14:45 momjian Exp $ */ +/* $PostgreSQL: pgsql/src/interfaces/ecpg/pgtypeslib/dt.h,v 1.40 2008/11/26 16:31:02 meskes Exp $ */ #ifndef DT_H #define DT_H @@ -25,6 +25,22 @@ typedef double fsec_t; #define USE_SQL_DATES 2 #define USE_GERMAN_DATES 3 +#define INTSTYLE_POSTGRES 0 +#define INTSTYLE_POSTGRES_VERBOSE 1 +#define INTSTYLE_SQL_STANDARD 2 +#define INTSTYLE_ISO_8601 3 + +#define INTERVAL_FULL_RANGE (0x7FFF) +#define INTERVAL_MASK(b) (1 << (b)) +#define MAX_INTERVAL_PRECISION 6 + +#define DTERR_BAD_FORMAT (-1) +#define DTERR_FIELD_OVERFLOW (-2) +#define DTERR_MD_FIELD_OVERFLOW (-3) /* triggers hint about DateStyle */ +#define DTERR_INTERVAL_OVERFLOW (-4) +#define DTERR_TZDISP_OVERFLOW (-5) + + #define DAGO "ago" #define EPOCH "epoch" #define INVALID "invalid" @@ -77,6 +93,9 @@ typedef double fsec_t; * Furthermore, the values for YEAR, MONTH, DAY, HOUR, MINUTE, SECOND * must be in the range 0..14 so that the associated bitmasks can fit * into the left half of an INTERVAL's typmod value. + * + * Copy&pasted these values from src/include/utils/datetime.h + * 2008-11-20, changing a number of their values. */ #define RESERV 0 @@ -92,20 +111,23 @@ typedef double fsec_t; #define HOUR 10 #define MINUTE 11 #define SECOND 12 -#define DOY 13 -#define DOW 14 -#define UNITS 15 -#define ADBC 16 +#define MILLISECOND 13 +#define MICROSECOND 14 +#define DOY 15 +#define DOW 16 +#define UNITS 17 +#define ADBC 18 /* these are only for relative dates */ -#define AGO 17 -#define ABS_BEFORE 18 -#define ABS_AFTER 19 +#define AGO 19 +#define ABS_BEFORE 20 +#define ABS_AFTER 21 /* generic fields to help with parsing */ -#define ISODATE 20 -#define ISOTIME 21 +#define ISODATE 22 +#define ISOTIME 23 /* reserved for unrecognized string values */ #define UNKNOWN_FIELD 31 + /* * Token field definitions for time parsing and decoding. * These need to fit into the datetkn table type. @@ -164,13 +186,13 @@ typedef double fsec_t; /* * Bit mask definitions for time parsing. */ - +/* Copy&pasted these values from src/include/utils/datetime.h */ #define DTK_M(t) (0x01 << (t)) - +#define DTK_ALL_SECS_M (DTK_M(SECOND) | DTK_M(MILLISECOND) | DTK_M(MICROSECOND)) #define DTK_DATE_M (DTK_M(YEAR) | DTK_M(MONTH) | DTK_M(DAY)) #define DTK_TIME_M (DTK_M(HOUR) | DTK_M(MINUTE) | DTK_M(SECOND)) -#define MAXDATELEN 51 /* maximum possible length of an input date +#define MAXDATELEN 63 /* maximum possible length of an input date * string (not counting tr. null) */ #define MAXDATEFIELDS 25 /* maximum possible number of fields in a date * string */ diff --git a/src/interfaces/ecpg/pgtypeslib/interval.c b/src/interfaces/ecpg/pgtypeslib/interval.c index a8f1899fdb..58b5fefff6 100644 --- a/src/interfaces/ecpg/pgtypeslib/interval.c +++ b/src/interfaces/ecpg/pgtypeslib/interval.c @@ -1,4 +1,4 @@ -/* $PostgreSQL: pgsql/src/interfaces/ecpg/pgtypeslib/interval.c,v 1.37 2007/08/22 08:20:58 meskes Exp $ */ +/* $PostgreSQL: pgsql/src/interfaces/ecpg/pgtypeslib/interval.c,v 1.38 2008/11/26 16:31:02 meskes Exp $ */ #include "postgres_fe.h" #include @@ -13,39 +13,347 @@ #include "pgtypes_error.h" #include "pgtypes_interval.h" -/* DecodeInterval() - * Interpret previously parsed fields for general time interval. - * Return 0 if decoded and -1 if problems. +/* copy&pasted from .../src/backend/utils/adt/datetime.c */ +static int +strtoi(const char *nptr, char **endptr, int base) +{ + long val; + + val = strtol(nptr, endptr, base); +#ifdef HAVE_LONG_INT_64 + if (val != (long) ((int32) val)) + errno = ERANGE; +#endif + return (int) val; +} + +/* copy&pasted from .../src/backend/utils/adt/datetime.c + * and changesd struct pg_tm to struct tm + */ +static void +AdjustFractSeconds(double frac, struct /*pg_*/tm * tm, fsec_t *fsec, int scale) +{ + int sec; + + if (frac == 0) + return; + frac *= scale; + sec = (int) frac; + tm->tm_sec += sec; + frac -= sec; +#ifdef HAVE_INT64_TIMESTAMP + *fsec += rint(frac * 1000000); +#else + *fsec += frac; +#endif +} + + +/* copy&pasted from .../src/backend/utils/adt/datetime.c + * and changesd struct pg_tm to struct tm + */ +static void +AdjustFractDays(double frac, struct /*pg_*/tm * tm, fsec_t *fsec, int scale) +{ + int extra_days; + + if (frac == 0) + return; + frac *= scale; + extra_days = (int) frac; + tm->tm_mday += extra_days; + frac -= extra_days; + AdjustFractSeconds(frac, tm, fsec, SECS_PER_DAY); +} + +/* copy&pasted from .../src/backend/utils/adt/datetime.c */ +static int +ParseISO8601Number(char *str, char **endptr, int *ipart, double *fpart) +{ + double val; + + if (!(isdigit((unsigned char) *str) || *str == '-' || *str == '.')) + return DTERR_BAD_FORMAT; + errno = 0; + val = strtod(str, endptr); + /* did we not see anything that looks like a double? */ + if (*endptr == str || errno != 0) + return DTERR_BAD_FORMAT; + /* watch out for overflow */ + if (val < INT_MIN || val > INT_MAX) + return DTERR_FIELD_OVERFLOW; + /* be very sure we truncate towards zero (cf dtrunc()) */ + if (val >= 0) + *ipart = (int) floor(val); + else + *ipart = (int) -floor(-val); + *fpart = val - *ipart; + return 0; +} + +/* copy&pasted from .../src/backend/utils/adt/datetime.c */ +static int +ISO8601IntegerWidth(char *fieldstart) +{ + /* We might have had a leading '-' */ + if (*fieldstart == '-') + fieldstart++; + return strspn(fieldstart, "0123456789"); +} + + +/* copy&pasted from .../src/backend/utils/adt/datetime.c + * and changesd struct pg_tm to struct tm + */ +static inline void +ClearPgTm(struct /*pg_*/tm *tm, fsec_t *fsec) +{ + tm->tm_year = 0; + tm->tm_mon = 0; + tm->tm_mday = 0; + tm->tm_hour = 0; + tm->tm_min = 0; + tm->tm_sec = 0; + *fsec = 0; +} + +/* copy&pasted from .../src/backend/utils/adt/datetime.c + * + * * changesd struct pg_tm to struct tm + * + * * Made the function static + */ +static int +DecodeISO8601Interval(char *str, + int *dtype, struct /*pg_*/tm * tm, fsec_t *fsec) +{ + bool datepart = true; + bool havefield = false; + + *dtype = DTK_DELTA; + ClearPgTm(tm, fsec); + + if (strlen(str) < 2 || str[0] != 'P') + return DTERR_BAD_FORMAT; + + str++; + while (*str) + { + char *fieldstart; + int val; + double fval; + char unit; + int dterr; + + if (*str == 'T') /* T indicates the beginning of the time part */ + { + datepart = false; + havefield = false; + str++; + continue; + } + + fieldstart = str; + dterr = ParseISO8601Number(str, &str, &val, &fval); + if (dterr) + return dterr; + + /* + * Note: we could step off the end of the string here. Code below + * *must* exit the loop if unit == '\0'. + */ + unit = *str++; + + if (datepart) + { + switch (unit) /* before T: Y M W D */ + { + case 'Y': + tm->tm_year += val; + tm->tm_mon += (fval * 12); + break; + case 'M': + tm->tm_mon += val; + AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH); + break; + case 'W': + tm->tm_mday += val * 7; + AdjustFractDays(fval, tm, fsec, 7); + break; + case 'D': + tm->tm_mday += val; + AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY); + break; + case 'T': /* ISO 8601 4.4.3.3 Alternative Format / Basic */ + case '\0': + if (ISO8601IntegerWidth(fieldstart) == 8 && !havefield) + { + tm->tm_year += val / 10000; + tm->tm_mon += (val / 100) % 100; + tm->tm_mday += val % 100; + AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY); + if (unit == '\0') + return 0; + datepart = false; + havefield = false; + continue; + } + /* Else fall through to extended alternative format */ + case '-': /* ISO 8601 4.4.3.3 Alternative Format, Extended */ + if (havefield) + return DTERR_BAD_FORMAT; + + tm->tm_year += val; + tm->tm_mon += (fval * 12); + if (unit == '\0') + return 0; + if (unit == 'T') + { + datepart = false; + havefield = false; + continue; + } + + dterr = ParseISO8601Number(str, &str, &val, &fval); + if (dterr) + return dterr; + tm->tm_mon += val; + AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH); + if (*str == '\0') + return 0; + if (*str == 'T') + { + datepart = false; + havefield = false; + continue; + } + if (*str != '-') + return DTERR_BAD_FORMAT; + str++; + + dterr = ParseISO8601Number(str, &str, &val, &fval); + if (dterr) + return dterr; + tm->tm_mday += val; + AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY); + if (*str == '\0') + return 0; + if (*str == 'T') + { + datepart = false; + havefield = false; + continue; + } + return DTERR_BAD_FORMAT; + default: + /* not a valid date unit suffix */ + return DTERR_BAD_FORMAT; + } + } + else + { + switch (unit) /* after T: H M S */ + { + case 'H': + tm->tm_hour += val; + AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR); + break; + case 'M': + tm->tm_min += val; + AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE); + break; + case 'S': + tm->tm_sec += val; + AdjustFractSeconds(fval, tm, fsec, 1); + break; + case '\0': /* ISO 8601 4.4.3.3 Alternative Format */ + if (ISO8601IntegerWidth(fieldstart) == 6 && !havefield) + { + tm->tm_hour += val / 10000; + tm->tm_min += (val / 100) % 100; + tm->tm_sec += val % 100; + AdjustFractSeconds(fval, tm, fsec, 1); + return 0; + } + /* Else fall through to extended alternative format */ + case ':': /* ISO 8601 4.4.3.3 Alternative Format, Extended */ + if (havefield) + return DTERR_BAD_FORMAT; + + tm->tm_hour += val; + AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR); + if (unit == '\0') + return 0; + + dterr = ParseISO8601Number(str, &str, &val, &fval); + if (dterr) + return dterr; + tm->tm_min += val; + AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE); + if (*str == '\0') + return 0; + if (*str != ':') + return DTERR_BAD_FORMAT; + str++; + + dterr = ParseISO8601Number(str, &str, &val, &fval); + if (dterr) + return dterr; + tm->tm_sec += val; + AdjustFractSeconds(fval, tm, fsec, 1); + if (*str == '\0') + return 0; + return DTERR_BAD_FORMAT; + + default: + /* not a valid time unit suffix */ + return DTERR_BAD_FORMAT; + } + } + + havefield = true; + } + + return 0; +} + + + +/* copy&pasted from .../src/backend/utils/adt/datetime.c + * with 3 exceptions + * + * * changesd struct pg_tm to struct tm + * + * * ECPG code called this without a 'range' parameter + * removed 'int range' from the argument list and + * places where DecodeTime is called; and added + * int range = INTERVAL_FULL_RANGE; * - * Allow "date" field DTK_DATE since this could be just - * an unsigned floating point number. - thomas 1997-11-16 + * * ECPG semes not to have a global IntervalStyle + * so added + * int IntervalStyle = INTSTYLE_POSTGRES; * - * Allow ISO-style time span, with implicit units on number of days - * preceding an hh:mm:ss field. - thomas 1998-04-30 + * * Assert wasn't available so removed it. */ int -DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct tm * tm, fsec_t *fsec) +DecodeInterval(char **field, int *ftype, int nf, /*int range,*/ + int *dtype, struct /*pg_*/tm * tm, fsec_t *fsec) { - int is_before = FALSE; - + int IntervalStyle = INTSTYLE_POSTGRES_VERBOSE; + int range = INTERVAL_FULL_RANGE; + bool is_before = FALSE; char *cp; int fmask = 0, tmask, type; int i; + int dterr; int val; double fval; *dtype = DTK_DELTA; - type = IGNORE_DTF; - tm->tm_year = 0; - tm->tm_mon = 0; - tm->tm_mday = 0; - tm->tm_hour = 0; - tm->tm_min = 0; - tm->tm_sec = 0; - *fsec = 0; + ClearPgTm(tm,fsec); /* read through list backwards to pick up units before values */ for (i = nf - 1; i >= 0; i--) @@ -53,8 +361,10 @@ DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct tm * tm, fse switch (ftype[i]) { case DTK_TIME: - if (DecodeTime(field[i], fmask, &tmask, tm, fsec) != 0) - return -1; + dterr = DecodeTime(field[i], fmask, /* range, */ + &tmask, tm, fsec); + if (dterr) + return dterr; type = DTK_DAY; break; @@ -62,18 +372,19 @@ DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct tm * tm, fse /* * Timezone is a token with a leading sign character and - * otherwise the same as a non-signed time field + * at least one digit; there could be ':', '.', '-' + * embedded in it as well. */ + /* Assert(*field[i] == '-' || *field[i] == '+'); */ /* - * A single signed number ends up here, but will be rejected - * by DecodeTime(). So, work this out to drop through to - * DTK_NUMBER, which *can* tolerate this. + * Try for hh:mm or hh:mm:ss. If not, fall through to + * DTK_NUMBER case, which can handle signed float numbers + * and signed year-month values. */ - cp = field[i] + 1; - while (*cp != '\0' && *cp != ':' && *cp != '.') - cp++; - if (*cp == ':' && DecodeTime((field[i] + 1), fmask, &tmask, tm, fsec) == 0) + if (strchr(field[i] + 1, ':') != NULL && + DecodeTime(field[i] + 1, fmask, /* INTERVAL_FULL_RANGE, */ + &tmask, tm, fsec) == 0) { if (*field[i] == '-') { @@ -93,47 +404,81 @@ DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct tm * tm, fse tmask = DTK_M(TZ); break; } - else if (type == IGNORE_DTF) + /* FALL THROUGH */ + + case DTK_DATE: + case DTK_NUMBER: + if (type == IGNORE_DTF) { - if (*cp == '.') + /* use typmod to decide what rightmost field is */ + switch (range) { - /* - * Got a decimal point? Then assume some sort of - * seconds specification - */ - type = DTK_SECOND; - } - else if (*cp == '\0') - { - /* - * Only a signed integer? Then must assume a - * timezone-like usage - */ - type = DTK_HOUR; + case INTERVAL_MASK(YEAR): + type = DTK_YEAR; + break; + case INTERVAL_MASK(MONTH): + case INTERVAL_MASK(YEAR) | INTERVAL_MASK(MONTH): + type = DTK_MONTH; + break; + case INTERVAL_MASK(DAY): + type = DTK_DAY; + break; + case INTERVAL_MASK(HOUR): + case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR): + case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE): + case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND): + type = DTK_HOUR; + break; + case INTERVAL_MASK(MINUTE): + case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE): + type = DTK_MINUTE; + break; + case INTERVAL_MASK(SECOND): + case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND): + case INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND): + type = DTK_SECOND; + break; + default: + type = DTK_SECOND; + break; } } - /* DROP THROUGH */ - case DTK_DATE: - case DTK_NUMBER: - val = strtol(field[i], &cp, 10); + errno = 0; + val = strtoi(field[i], &cp, 10); + if (errno == ERANGE) + return DTERR_FIELD_OVERFLOW; - if (type == IGNORE_DTF) - type = DTK_SECOND; + if (*cp == '-') + { + /* SQL "years-months" syntax */ + int val2; - if (*cp == '.') + val2 = strtoi(cp + 1, &cp, 10); + if (errno == ERANGE || val2 < 0 || val2 >= MONTHS_PER_YEAR) + return DTERR_FIELD_OVERFLOW; + if (*cp != '\0') + return DTERR_BAD_FORMAT; + type = DTK_MONTH; + if (*field[i] == '-') + val2 = -val2; + val = val * MONTHS_PER_YEAR + val2; + fval = 0; + } + else if (*cp == '.') { + errno = 0; fval = strtod(cp, &cp); - if (*cp != '\0') - return -1; + if (*cp != '\0' || errno != 0) + return DTERR_BAD_FORMAT; - if (val < 0) + if (*field[i] == '-') fval = -fval; } else if (*cp == '\0') fval = 0; else - return -1; + return DTERR_BAD_FORMAT; tmask = 0; /* DTK_M(type); */ @@ -141,135 +486,68 @@ DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct tm * tm, fse { case DTK_MICROSEC: #ifdef HAVE_INT64_TIMESTAMP - *fsec += val + fval; + *fsec += rint(val + fval); #else *fsec += (val + fval) * 1e-6; #endif + tmask = DTK_M(MICROSECOND); break; case DTK_MILLISEC: #ifdef HAVE_INT64_TIMESTAMP - *fsec += (val + fval) * 1000; + *fsec += rint((val + fval) * 1000); #else *fsec += (val + fval) * 1e-3; #endif + tmask = DTK_M(MILLISECOND); break; case DTK_SECOND: tm->tm_sec += val; #ifdef HAVE_INT64_TIMESTAMP - *fsec += fval * 1000000; + *fsec += rint(fval * 1000000); #else *fsec += fval; #endif - tmask = DTK_M(SECOND); + + /* + * If any subseconds were specified, consider this + * microsecond and millisecond input as well. + */ + if (fval == 0) + tmask = DTK_M(SECOND); + else + tmask = DTK_ALL_SECS_M; break; case DTK_MINUTE: tm->tm_min += val; - if (fval != 0) - { - int sec; - - fval *= SECS_PER_MINUTE; - sec = fval; - tm->tm_sec += sec; -#ifdef HAVE_INT64_TIMESTAMP - *fsec += ((fval - sec) * 1000000); -#else - *fsec += fval - sec; -#endif - } + AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE); tmask = DTK_M(MINUTE); break; case DTK_HOUR: tm->tm_hour += val; - if (fval != 0) - { - int sec; - - fval *= SECS_PER_HOUR; - sec = fval; - tm->tm_sec += sec; -#ifdef HAVE_INT64_TIMESTAMP - *fsec += (fval - sec) * 1000000; -#else - *fsec += fval - sec; -#endif - } + AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR); tmask = DTK_M(HOUR); + type = DTK_DAY; break; case DTK_DAY: tm->tm_mday += val; - if (fval != 0) - { - int sec; - - fval *= SECS_PER_DAY; - sec = fval; - tm->tm_sec += sec; -#ifdef HAVE_INT64_TIMESTAMP - *fsec += (fval - sec) * 1000000; -#else - *fsec += fval - sec; -#endif - } + AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY); tmask = (fmask & DTK_M(DAY)) ? 0 : DTK_M(DAY); break; case DTK_WEEK: tm->tm_mday += val * 7; - if (fval != 0) - { - int extra_days; - - fval *= 7; - extra_days = (int32) fval; - tm->tm_mday += extra_days; - fval -= extra_days; - if (fval != 0) - { - int sec; - - fval *= SECS_PER_DAY; - sec = fval; - tm->tm_sec += sec; -#ifdef HAVE_INT64_TIMESTAMP - *fsec += (fval - sec) * 1000000; -#else - *fsec += fval - sec; -#endif - } - } + AdjustFractDays(fval, tm, fsec, 7); tmask = (fmask & DTK_M(DAY)) ? 0 : DTK_M(DAY); break; case DTK_MONTH: tm->tm_mon += val; - if (fval != 0) - { - int day; - - fval *= DAYS_PER_MONTH; - day = fval; - tm->tm_mday += day; - fval -= day; - if (fval != 0) - { - int sec; - - fval *= SECS_PER_DAY; - sec = fval; - tm->tm_sec += sec; -#ifdef HAVE_INT64_TIMESTAMP - *fsec += (fval - sec) * 1000000; -#else - *fsec += fval - sec; -#endif - } - } + AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH); tmask = DTK_M(MONTH); break; @@ -302,7 +580,7 @@ DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct tm * tm, fse break; default: - return -1; + return DTERR_BAD_FORMAT; } break; @@ -330,19 +608,24 @@ DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct tm * tm, fse break; default: - return -1; + return DTERR_BAD_FORMAT; } break; default: - return -1; + return DTERR_BAD_FORMAT; } if (tmask & fmask) - return -1; + return DTERR_BAD_FORMAT; fmask |= tmask; } + /* ensure that at least one time field has been found */ + if (fmask == 0) + return DTERR_BAD_FORMAT; + + /* ensure fractional seconds are fractional */ if (*fsec != 0) { int sec; @@ -356,250 +639,344 @@ DecodeInterval(char **field, int *ftype, int nf, int *dtype, struct tm * tm, fse tm->tm_sec += sec; } + /*---------- + * The SQL standard defines the interval literal + * '-1 1:00:00' + * to mean "negative 1 days and negative 1 hours", while Postgres + * traditionally treats this as meaning "negative 1 days and positive + * 1 hours". In SQL_STANDARD intervalstyle, we apply the leading sign + * to all fields if there are no other explicit signs. + * + * We leave the signs alone if there are additional explicit signs. + * This protects us against misinterpreting postgres-style dump output, + * since the postgres-style output code has always put an explicit sign on + * all fields following a negative field. But note that SQL-spec output + * is ambiguous and can be misinterpreted on load! (So it's best practice + * to dump in postgres style, not SQL style.) + *---------- + */ + if (IntervalStyle == INTSTYLE_SQL_STANDARD && *field[0] == '-') + { + /* Check for additional explicit signs */ + bool more_signs = false; + + for (i = 1; i < nf; i++) + { + if (*field[i] == '-' || *field[i] == '+') + { + more_signs = true; + break; + } + } + + if (!more_signs) + { + /* + * Rather than re-determining which field was field[0], just + * force 'em all negative. + */ + if (*fsec > 0) + *fsec = -(*fsec); + if (tm->tm_sec > 0) + tm->tm_sec = -tm->tm_sec; + if (tm->tm_min > 0) + tm->tm_min = -tm->tm_min; + if (tm->tm_hour > 0) + tm->tm_hour = -tm->tm_hour; + if (tm->tm_mday > 0) + tm->tm_mday = -tm->tm_mday; + if (tm->tm_mon > 0) + tm->tm_mon = -tm->tm_mon; + if (tm->tm_year > 0) + tm->tm_year = -tm->tm_year; + } + } + + /* finally, AGO negates everything */ if (is_before) { *fsec = -(*fsec); - tm->tm_sec = -(tm->tm_sec); - tm->tm_min = -(tm->tm_min); - tm->tm_hour = -(tm->tm_hour); - tm->tm_mday = -(tm->tm_mday); - tm->tm_mon = -(tm->tm_mon); - tm->tm_year = -(tm->tm_year); + tm->tm_sec = -tm->tm_sec; + tm->tm_min = -tm->tm_min; + tm->tm_hour = -tm->tm_hour; + tm->tm_mday = -tm->tm_mday; + tm->tm_mon = -tm->tm_mon; + tm->tm_year = -tm->tm_year; } - /* ensure that at least one time field has been found */ - return (fmask != 0) ? 0 : -1; -} /* DecodeInterval() */ + return 0; +} -/* EncodeInterval() - * Interpret time structure as a delta time and convert to string. - * - * Support "traditional Postgres" and ISO-8601 styles. - * Actually, afaik ISO does not address time interval formatting, - * but this looks similar to the spec for absolute date/time. - * - thomas 1998-04-30 + +/* copy&pasted from .../src/backend/utils/adt/datetime.c */ +static char * +AddVerboseIntPart(char *cp, int value, const char *units, + bool *is_zero, bool *is_before) +{ + if (value == 0) + return cp; + /* first nonzero value sets is_before */ + if (*is_zero) + { + *is_before = (value < 0); + value = abs(value); + } + else if (*is_before) + value = -value; + sprintf(cp, " %d %s%s", value, units, (value == 1) ? "" : "s"); + *is_zero = FALSE; + return cp + strlen(cp); +} + +/* copy&pasted from .../src/backend/utils/adt/datetime.c */ +static char * +AddPostgresIntPart(char *cp, int value, const char *units, + bool *is_zero, bool *is_before) +{ + if (value == 0) + return cp; + sprintf(cp, "%s%s%d %s%s", + (!*is_zero) ? " " : "", + (*is_before && value > 0) ? "+" : "", + value, + units, + (value != 1) ? "s" : ""); + /* + * Each nonzero field sets is_before for (only) the next one. This is + * a tad bizarre but it's how it worked before... + */ + *is_before = (value < 0); + *is_zero = FALSE; + return cp + strlen(cp); +} + +/* copy&pasted from .../src/backend/utils/adt/datetime.c */ +static char * +AddISO8601IntPart(char *cp, int value, char units) +{ + if (value == 0) + return cp; + sprintf(cp, "%d%c", value, units); + return cp + strlen(cp); +} + +/* copy&pasted from .../src/backend/utils/adt/datetime.c */ +static void +AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros) +{ + if (fsec == 0) + { + if (fillzeros) + sprintf(cp, "%02d", abs(sec)); + else + sprintf(cp, "%d", 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); + } +} + + +/* copy&pasted from .../src/backend/utils/adt/datetime.c + * + * Change pg_tm to tm */ + int -EncodeInterval(struct tm * tm, fsec_t fsec, int style, char *str) +EncodeInterval(struct /*pg_*/tm * tm, fsec_t fsec, int style, char *str) { - int is_before = FALSE; - int is_nonzero = FALSE; + char *cp = str; + int year = tm->tm_year; + int mon = tm->tm_mon; + int mday = tm->tm_mday; + int hour = tm->tm_hour; + int min = tm->tm_min; + int sec = tm->tm_sec; + bool is_before = FALSE; + bool is_zero = TRUE; /* * The sign of year and month are guaranteed to match, since they are * stored internally as "month". But we'll need to check for is_before and - * is_nonzero when determining the signs of hour/minute/seconds fields. + * is_zero when determining the signs of day and hour/minute/seconds + * fields. */ switch (style) { - /* compatible with ISO date formats */ - case USE_ISO_DATES: - if (tm->tm_year != 0) - { - sprintf(cp, "%d year%s", - tm->tm_year, (tm->tm_year != 1) ? "s" : ""); - cp += strlen(cp); - is_before = (tm->tm_year < 0); - is_nonzero = TRUE; - } - - if (tm->tm_mon != 0) - { - sprintf(cp, "%s%s%d mon%s", is_nonzero ? " " : "", - (is_before && tm->tm_mon > 0) ? "+" : "", - tm->tm_mon, (tm->tm_mon != 1) ? "s" : ""); - cp += strlen(cp); - is_before = (tm->tm_mon < 0); - is_nonzero = TRUE; - } - - if (tm->tm_mday != 0) - { - sprintf(cp, "%s%s%d day%s", is_nonzero ? " " : "", - (is_before && tm->tm_mday > 0) ? "+" : "", - tm->tm_mday, (tm->tm_mday != 1) ? "s" : ""); - cp += strlen(cp); - is_before = (tm->tm_mday < 0); - is_nonzero = TRUE; - } - if (!is_nonzero || tm->tm_hour != 0 || tm->tm_min != 0 || - tm->tm_sec != 0 || fsec != 0) + /* SQL Standard interval format */ + case INTSTYLE_SQL_STANDARD: { - int minus = tm->tm_hour < 0 || tm->tm_min < 0 || - tm->tm_sec < 0 || fsec < 0; + bool has_negative = year < 0 || mon < 0 || + mday < 0 || hour < 0 || + min < 0 || sec < 0 || fsec < 0; + bool has_positive = year > 0 || mon > 0 || + mday > 0 || hour > 0 || + min > 0 || sec > 0 || fsec > 0; + bool has_year_month = year != 0 || mon != 0; + bool has_day_time = mday != 0 || hour != 0 || + min != 0 || sec != 0 || fsec != 0; + bool has_day = mday != 0; + bool sql_standard_value = !(has_negative && has_positive) && + !(has_year_month && has_day_time); - sprintf(cp, "%s%s%02d:%02d", (is_nonzero ? " " : ""), - (minus ? "-" : (is_before ? "+" : "")), - abs(tm->tm_hour), abs(tm->tm_min)); - cp += strlen(cp); - /* Mark as "non-zero" since the fields are now filled in */ - is_nonzero = TRUE; + /* + * SQL Standard wants only 1 "" preceding the whole + * interval ... but can't do that if mixed signs. + */ + if (has_negative && sql_standard_value) + { + *cp++ = '-'; + year = -year; + mon = -mon; + mday = -mday; + hour = -hour; + min = -min; + sec = -sec; + fsec = -fsec; + } - /* fractional seconds? */ - if (fsec != 0) + if (!has_negative && !has_positive) { -#ifdef HAVE_INT64_TIMESTAMP - sprintf(cp, ":%02d", abs(tm->tm_sec)); + sprintf(cp, "0"); + } + else if (!sql_standard_value) + { + /* + * For non sql-standard interval values, + * force outputting the signs to avoid + * ambiguities with intervals with mixed + * sign components. + */ + char year_sign = (year < 0 || mon < 0) ? '-' : '+'; + char day_sign = (mday < 0) ? '-' : '+'; + char sec_sign = (hour < 0 || min < 0 || + sec < 0 || fsec < 0) ? '-' : '+'; + + sprintf(cp, "%c%d-%d %c%d %c%d:%02d:", + year_sign, abs(year), abs(mon), + day_sign, abs(mday), + sec_sign, abs(hour), abs(min)); cp += strlen(cp); - sprintf(cp, ".%06d", Abs(fsec)); -#else - fsec += tm->tm_sec; - sprintf(cp, ":%012.9f", fabs(fsec)); -#endif - TrimTrailingZeros(cp); + AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true); + } + else if (has_year_month) + { + sprintf(cp, "%d-%d", year, mon); + } + else if (has_day) + { + sprintf(cp, "%d %d:%02d:", mday, hour, min); cp += strlen(cp); - is_nonzero = TRUE; + AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true); } - /* otherwise, integer seconds only? */ - else if (tm->tm_sec != 0) + else { - sprintf(cp, ":%02d", abs(tm->tm_sec)); + sprintf(cp, "%d:%02d:", hour, min); cp += strlen(cp); - is_nonzero = TRUE; + AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true); } } break; - case USE_POSTGRES_DATES: - default: - strcpy(cp, "@ "); - cp += strlen(cp); - - if (tm->tm_year != 0) + /* ISO 8601 "time-intervals by duration only" */ + case INTSTYLE_ISO_8601: + /* special-case zero to avoid printing nothing */ + if (year == 0 && mon == 0 && mday == 0 && + hour == 0 && min == 0 && sec == 0 && fsec == 0) { - int year = tm->tm_year; - - if (tm->tm_year < 0) - year = -year; - - sprintf(cp, "%d year%s", year, - (year != 1) ? "s" : ""); - cp += strlen(cp); - is_before = (tm->tm_year < 0); - is_nonzero = TRUE; - } - - if (tm->tm_mon != 0) - { - int mon = tm->tm_mon; - - if (is_before || (!is_nonzero && tm->tm_mon < 0)) - mon = -mon; - - sprintf(cp, "%s%d mon%s", is_nonzero ? " " : "", mon, - (mon != 1) ? "s" : ""); - cp += strlen(cp); - if (!is_nonzero) - is_before = (tm->tm_mon < 0); - is_nonzero = TRUE; - } - - if (tm->tm_mday != 0) - { - int day = tm->tm_mday; - - if (is_before || (!is_nonzero && tm->tm_mday < 0)) - day = -day; - - sprintf(cp, "%s%d day%s", is_nonzero ? " " : "", day, - (day != 1) ? "s" : ""); - cp += strlen(cp); - if (!is_nonzero) - is_before = (tm->tm_mday < 0); - is_nonzero = TRUE; + sprintf(cp, "PT0S"); + break; } - if (tm->tm_hour != 0) + *cp++ = 'P'; + cp = AddISO8601IntPart(cp, year, 'Y'); + cp = AddISO8601IntPart(cp, mon , 'M'); + cp = AddISO8601IntPart(cp, mday, 'D'); + if (hour != 0 || min != 0 || sec != 0 || fsec != 0) + *cp++ = 'T'; + cp = AddISO8601IntPart(cp, hour, 'H'); + cp = AddISO8601IntPart(cp, min , 'M'); + if (sec != 0 || fsec != 0) { - int hour = tm->tm_hour; - - if (is_before || (!is_nonzero && tm->tm_hour < 0)) - hour = -hour; - - sprintf(cp, "%s%d hour%s", is_nonzero ? " " : "", hour, - (hour != 1) ? "s" : ""); + if (sec < 0 || fsec < 0) + *cp++ = '-'; + AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false); cp += strlen(cp); - if (!is_nonzero) - is_before = (tm->tm_hour < 0); - is_nonzero = TRUE; + *cp++ = 'S'; + *cp++ = '\0'; } + break; - if (tm->tm_min != 0) + /* Compatible with postgresql < 8.4 when DateStyle = 'iso' */ + case INTSTYLE_POSTGRES: + cp = AddPostgresIntPart(cp, year, "year", &is_zero, &is_before); + cp = AddPostgresIntPart(cp, mon, "mon", &is_zero, &is_before); + cp = AddPostgresIntPart(cp, mday, "day", &is_zero, &is_before); + if (is_zero || hour != 0 || min != 0 || sec != 0 || fsec != 0) { - int min = tm->tm_min; + bool minus = (hour < 0 || min < 0 || sec < 0 || fsec < 0); - if (is_before || (!is_nonzero && tm->tm_min < 0)) - min = -min; - - sprintf(cp, "%s%d min%s", is_nonzero ? " " : "", min, - (min != 1) ? "s" : ""); + sprintf(cp, "%s%s%02d:%02d:", + is_zero ? "" : " ", + (minus ? "-" : (is_before ? "+" : "")), + abs(hour), abs(min)); cp += strlen(cp); - if (!is_nonzero) - is_before = (tm->tm_min < 0); - is_nonzero = TRUE; + AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true); } + break; - /* fractional seconds? */ - if (fsec != 0) - { -#ifdef HAVE_INT64_TIMESTAMP - if (is_before || (!is_nonzero && tm->tm_sec < 0)) - tm->tm_sec = -tm->tm_sec; - sprintf(cp, "%s%d.%02d secs", is_nonzero ? " " : "", - tm->tm_sec, ((int) fsec) / 10000); - cp += strlen(cp); - if (!is_nonzero) - is_before = (fsec < 0); -#else - fsec_t sec; - - fsec += tm->tm_sec; - sec = fsec; - if (is_before || (!is_nonzero && fsec < 0)) - sec = -sec; - - sprintf(cp, "%s%.2f secs", is_nonzero ? " " : "", sec); - cp += strlen(cp); - if (!is_nonzero) - is_before = (fsec < 0); -#endif - is_nonzero = TRUE; - - /* otherwise, integer seconds only? */ - } - else if (tm->tm_sec != 0) + /* Compatible with postgresql < 8.4 when DateStyle != 'iso' */ + case INTSTYLE_POSTGRES_VERBOSE: + default: + strcpy(cp, "@"); + cp++; + cp = AddVerboseIntPart(cp, year, "year", &is_zero, &is_before); + cp = AddVerboseIntPart(cp, mon, "mon", &is_zero, &is_before); + cp = AddVerboseIntPart(cp, mday, "day", &is_zero, &is_before); + cp = AddVerboseIntPart(cp, hour, "hour", &is_zero, &is_before); + cp = AddVerboseIntPart(cp, min, "min", &is_zero, &is_before); + if (sec != 0 || fsec != 0) { - int sec = tm->tm_sec; - - if (is_before || (!is_nonzero && tm->tm_sec < 0)) - sec = -sec; - - sprintf(cp, "%s%d sec%s", is_nonzero ? " " : "", sec, - (sec != 1) ? "s" : ""); + *cp++ = ' '; + if (sec < 0 || (sec == 0 && fsec < 0)) + { + if (is_zero) + is_before = TRUE; + else if (!is_before) + *cp++ = '-'; + } + else if (is_before) + *cp++ = '-'; + AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false); cp += strlen(cp); - if (!is_nonzero) - is_before = (tm->tm_sec < 0); - is_nonzero = TRUE; + sprintf(cp, " sec%s", + (abs(sec) != 1 || fsec != 0) ? "s" : ""); + is_zero = FALSE; } + /* identically zero? then put in a unitless zero... */ + if (is_zero) + strcat(cp, " 0"); + if (is_before) + strcat(cp, " ago"); break; } - /* identically zero? then put in a unitless zero... */ - if (!is_nonzero) - { - strcat(cp, "0"); - cp += strlen(cp); - } - - if (is_before && (style != USE_ISO_DATES)) - { - strcat(cp, " ago"); - cp += strlen(cp); - } - return 0; } /* EncodeInterval() */ + /* interval2tm() * Convert a interval data type to a tm structure. */ @@ -719,7 +1096,8 @@ PGTYPESinterval_from_asc(char *str, char **endptr) } if (ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf, ptr) != 0 || - DecodeInterval(field, ftype, nf, &dtype, tm, &fsec) != 0) + (DecodeInterval(field, ftype, nf, &dtype, tm, &fsec) != 0 && + DecodeISO8601Interval(str, &dtype, tm, &fsec) != 0)) { errno = PGTYPES_INTVL_BAD_INTERVAL; return NULL; @@ -754,7 +1132,7 @@ PGTYPESinterval_to_asc(interval * span) *tm = &tt; fsec_t fsec; char buf[MAXDATELEN + 1]; - int DateStyle = 0; + int IntervalStyle = INTSTYLE_POSTGRES_VERBOSE; if (interval2tm(*span, tm, &fsec) != 0) { @@ -762,7 +1140,7 @@ PGTYPESinterval_to_asc(interval * span) return NULL; } - if (EncodeInterval(tm, fsec, DateStyle, buf) != 0) + if (EncodeInterval(tm, fsec, IntervalStyle, buf) != 0) { errno = PGTYPES_INTVL_BAD_INTERVAL; return NULL; diff --git a/src/interfaces/ecpg/test/expected/pgtypeslib-dt_test.c b/src/interfaces/ecpg/test/expected/pgtypeslib-dt_test.c index 2dd263267d..f71d3ab98d 100644 --- a/src/interfaces/ecpg/test/expected/pgtypeslib-dt_test.c +++ b/src/interfaces/ecpg/test/expected/pgtypeslib-dt_test.c @@ -77,6 +77,12 @@ if (sqlca.sqlcode < 0) sqlprint ( );} if (sqlca.sqlcode < 0) sqlprint ( );} #line 30 "dt_test.pgc" + { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "set intervalstyle to postgres_verbose", ECPGt_EOIT, ECPGt_EORT); +#line 31 "dt_test.pgc" + +if (sqlca.sqlcode < 0) sqlprint ( );} +#line 31 "dt_test.pgc" + date1 = PGTYPESdate_from_asc(d1, NULL); ts1 = PGTYPEStimestamp_from_asc(t1, NULL); @@ -86,10 +92,10 @@ if (sqlca.sqlcode < 0) sqlprint ( );} ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_timestamp,&(ts1),(long)1,(long)1,sizeof(timestamp), ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EOIT, ECPGt_EORT); -#line 35 "dt_test.pgc" +#line 36 "dt_test.pgc" if (sqlca.sqlcode < 0) sqlprint ( );} -#line 35 "dt_test.pgc" +#line 36 "dt_test.pgc" { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select * from date_test where d = $1 ", @@ -99,10 +105,10 @@ if (sqlca.sqlcode < 0) sqlprint ( );} ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_timestamp,&(ts1),(long)1,(long)1,sizeof(timestamp), ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT); -#line 37 "dt_test.pgc" +#line 38 "dt_test.pgc" if (sqlca.sqlcode < 0) sqlprint ( );} -#line 37 "dt_test.pgc" +#line 38 "dt_test.pgc" text = PGTYPESdate_to_asc(date1); @@ -417,16 +423,16 @@ if (sqlca.sqlcode < 0) sqlprint ( );} free(text); { ECPGtrans(__LINE__, NULL, "rollback "); -#line 350 "dt_test.pgc" +#line 351 "dt_test.pgc" if (sqlca.sqlcode < 0) sqlprint ( );} -#line 350 "dt_test.pgc" +#line 351 "dt_test.pgc" { ECPGdisconnect(__LINE__, "CURRENT"); -#line 351 "dt_test.pgc" +#line 352 "dt_test.pgc" if (sqlca.sqlcode < 0) sqlprint ( );} -#line 351 "dt_test.pgc" +#line 352 "dt_test.pgc" return (0); diff --git a/src/interfaces/ecpg/test/expected/pgtypeslib-dt_test.stderr b/src/interfaces/ecpg/test/expected/pgtypeslib-dt_test.stderr index 7dced6a746..3aaee055ce 100644 --- a/src/interfaces/ecpg/test/expected/pgtypeslib-dt_test.stderr +++ b/src/interfaces/ecpg/test/expected/pgtypeslib-dt_test.stderr @@ -14,29 +14,35 @@ [NO_PID]: sqlca: code: 0, state: 00000 [NO_PID]: ecpg_execute on line 30: OK: SET [NO_PID]: sqlca: code: 0, state: 00000 -[NO_PID]: ecpg_execute on line 35: query: insert into date_test ( d , ts ) values ( $1 , $2 ) ; with 2 parameter(s) on connection regress1 +[NO_PID]: ecpg_execute on line 31: query: set intervalstyle to postgres_verbose; with 0 parameter(s) on connection regress1 [NO_PID]: sqlca: code: 0, state: 00000 -[NO_PID]: ecpg_execute on line 35: using PQexecParams +[NO_PID]: ecpg_execute on line 31: using PQexec [NO_PID]: sqlca: code: 0, state: 00000 -[NO_PID]: free_params on line 35: parameter 1 = 1966-01-17 +[NO_PID]: ecpg_execute on line 31: OK: SET [NO_PID]: sqlca: code: 0, state: 00000 -[NO_PID]: free_params on line 35: parameter 2 = 2000-07-12 17:34:29 +[NO_PID]: ecpg_execute on line 36: query: insert into date_test ( d , ts ) values ( $1 , $2 ) ; with 2 parameter(s) on connection regress1 [NO_PID]: sqlca: code: 0, state: 00000 -[NO_PID]: ecpg_execute on line 35: OK: INSERT 0 1 +[NO_PID]: ecpg_execute on line 36: using PQexecParams [NO_PID]: sqlca: code: 0, state: 00000 -[NO_PID]: ecpg_execute on line 37: query: select * from date_test where d = $1 ; with 1 parameter(s) on connection regress1 +[NO_PID]: free_params on line 36: parameter 1 = 1966-01-17 [NO_PID]: sqlca: code: 0, state: 00000 -[NO_PID]: ecpg_execute on line 37: using PQexecParams +[NO_PID]: free_params on line 36: parameter 2 = 2000-07-12 17:34:29 [NO_PID]: sqlca: code: 0, state: 00000 -[NO_PID]: free_params on line 37: parameter 1 = 1966-01-17 +[NO_PID]: ecpg_execute on line 36: OK: INSERT 0 1 [NO_PID]: sqlca: code: 0, state: 00000 -[NO_PID]: ecpg_execute on line 37: correctly got 1 tuples with 2 fields +[NO_PID]: ecpg_execute on line 38: query: select * from date_test where d = $1 ; with 1 parameter(s) on connection regress1 [NO_PID]: sqlca: code: 0, state: 00000 -[NO_PID]: ecpg_get_data on line 37: RESULT: 1966-01-17 offset: -1; array: yes +[NO_PID]: ecpg_execute on line 38: using PQexecParams [NO_PID]: sqlca: code: 0, state: 00000 -[NO_PID]: ecpg_get_data on line 37: RESULT: 2000-07-12 17:34:29 offset: -1; array: yes +[NO_PID]: free_params on line 38: parameter 1 = 1966-01-17 [NO_PID]: sqlca: code: 0, state: 00000 -[NO_PID]: ECPGtrans on line 350: action "rollback "; connection "regress1" +[NO_PID]: ecpg_execute on line 38: correctly got 1 tuples with 2 fields +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_get_data on line 38: RESULT: 1966-01-17 offset: -1; array: yes +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_get_data on line 38: RESULT: 2000-07-12 17:34:29 offset: -1; array: yes +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ECPGtrans on line 351: action "rollback "; connection "regress1" [NO_PID]: sqlca: code: 0, state: 00000 [NO_PID]: ecpg_finish: connection regress1 closed [NO_PID]: sqlca: code: 0, state: 00000 diff --git a/src/interfaces/ecpg/test/pgtypeslib/dt_test.pgc b/src/interfaces/ecpg/test/pgtypeslib/dt_test.pgc index d6c166477f..fcf39cecc7 100644 --- a/src/interfaces/ecpg/test/pgtypeslib/dt_test.pgc +++ b/src/interfaces/ecpg/test/pgtypeslib/dt_test.pgc @@ -28,6 +28,7 @@ main(void) exec sql connect to REGRESSDB1; exec sql create table date_test (d date, ts timestamp); exec sql set datestyle to iso; + exec sql set intervalstyle to postgres_verbose; date1 = PGTYPESdate_from_asc(d1, NULL); ts1 = PGTYPEStimestamp_from_asc(t1, NULL); -- 2.40.0