Commit 7c5a561f authored by Michael Meskes's avatar Michael Meskes

Applied patch by Ron Mayer <rm_pg@cheapcomplexdevices.com> to merge the new

interval style into ecpg.
parent cbb3e1cd
......@@ -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 <rm_pg@cheapcomplexdevices.com> 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.
......
/* $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 */
......
/* $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 <time.h>
......@@ -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 "<sign>" 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;
......
......@@ -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);
......
......@@ -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
......@@ -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);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment