diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c index d66f640fe6..f9dfb787c3 100644 --- a/src/backend/utils/adt/date.c +++ b/src/backend/utils/adt/date.c @@ -40,7 +40,6 @@ #endif -static void EncodeSpecialDate(DateADT dt, char *str); static int time2tm(TimeADT time, struct pg_tm * tm, fsec_t *fsec); static int timetz2tm(TimeTzADT *time, struct pg_tm * tm, fsec_t *fsec, int *tzp); static int tm2time(struct pg_tm * tm, fsec_t fsec, TimeADT *result); @@ -273,7 +272,7 @@ make_date(PG_FUNCTION_ARGS) /* * Convert reserved date values to string. */ -static void +void EncodeSpecialDate(DateADT dt, char *str) { if (DATE_IS_NOBEGIN(dt)) diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c index f394942bc3..af97fc1eff 100644 --- a/src/backend/utils/adt/json.c +++ b/src/backend/utils/adt/json.c @@ -32,9 +32,6 @@ #include "utils/typcache.h" #include "utils/syscache.h" -/* String to output for infinite dates and timestamps */ -#define DT_INFINITY "\"infinity\"" - /* * The context of the parser is maintained by the recursive descent * mechanism, but is passed explicitly to the error reporting routine @@ -70,11 +67,11 @@ typedef enum /* type categories for datum_to_json */ typedef struct JsonAggState { - StringInfo str; - JsonTypeCategory key_category; - Oid key_output_func; - JsonTypeCategory val_category; - Oid val_output_func; + StringInfo str; + JsonTypeCategory key_category; + Oid key_output_func; + JsonTypeCategory val_category; + Oid val_output_func; } JsonAggState; static inline void json_lex(JsonLexContext *lex); @@ -360,16 +357,16 @@ pg_parse_json(JsonLexContext *lex, JsonSemAction *sem) int json_count_array_elements(JsonLexContext *lex) { - JsonLexContext copylex; - int count; + JsonLexContext copylex; + int count; /* * It's safe to do this with a shallow copy because the lexical routines - * don't scribble on the input. They do scribble on the other pointers etc, - * so doing this with a copy makes that safe. + * don't scribble on the input. They do scribble on the other pointers + * etc, so doing this with a copy makes that safe. */ memcpy(©lex, lex, sizeof(JsonLexContext)); - copylex.strval = NULL; /* not interested in values here */ + copylex.strval = NULL; /* not interested in values here */ copylex.lex_level++; count = 0; @@ -1492,19 +1489,16 @@ datum_to_json(Datum val, bool is_null, StringInfo result, char buf[MAXDATELEN + 1]; date = DatumGetDateADT(val); - + /* Same as date_out(), but forcing DateStyle */ if (DATE_NOT_FINITE(date)) - { - /* we have to format infinity ourselves */ - appendStringInfoString(result, DT_INFINITY); - } + EncodeSpecialDate(date, buf); else { j2date(date + POSTGRES_EPOCH_JDATE, &(tm.tm_year), &(tm.tm_mon), &(tm.tm_mday)); EncodeDateOnly(&tm, USE_XSD_DATES, buf); - appendStringInfo(result, "\"%s\"", buf); } + appendStringInfo(result, "\"%s\"", buf); } break; case JSONTYPE_TIMESTAMP: @@ -1515,21 +1509,16 @@ datum_to_json(Datum val, bool is_null, StringInfo result, char buf[MAXDATELEN + 1]; timestamp = DatumGetTimestamp(val); - + /* Same as timestamp_out(), but forcing DateStyle */ if (TIMESTAMP_NOT_FINITE(timestamp)) - { - /* we have to format infinity ourselves */ - appendStringInfoString(result, DT_INFINITY); - } + EncodeSpecialTimestamp(timestamp, buf); else if (timestamp2tm(timestamp, NULL, &tm, &fsec, NULL, NULL) == 0) - { EncodeDateTime(&tm, fsec, false, 0, NULL, USE_XSD_DATES, buf); - appendStringInfo(result, "\"%s\"", buf); - } else ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); + appendStringInfo(result, "\"%s\"", buf); } break; case JSONTYPE_TIMESTAMPTZ: @@ -1541,22 +1530,17 @@ datum_to_json(Datum val, bool is_null, StringInfo result, const char *tzn = NULL; char buf[MAXDATELEN + 1]; - timestamp = DatumGetTimestamp(val); - + timestamp = DatumGetTimestampTz(val); + /* Same as timestamptz_out(), but forcing DateStyle */ if (TIMESTAMP_NOT_FINITE(timestamp)) - { - /* we have to format infinity ourselves */ - appendStringInfoString(result, DT_INFINITY); - } + EncodeSpecialTimestamp(timestamp, buf); else if (timestamp2tm(timestamp, &tz, &tm, &fsec, &tzn, NULL) == 0) - { EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf); - appendStringInfo(result, "\"%s\"", buf); - } else ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); + appendStringInfo(result, "\"%s\"", buf); } break; case JSONTYPE_JSON: @@ -1875,7 +1859,7 @@ json_agg_transfn(PG_FUNCTION_ARGS) { MemoryContext aggcontext, oldcontext; - JsonAggState *state; + JsonAggState *state; Datum val; if (!AggCheckCallContext(fcinfo, &aggcontext)) @@ -1886,7 +1870,7 @@ json_agg_transfn(PG_FUNCTION_ARGS) if (PG_ARGISNULL(0)) { - Oid arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1); + Oid arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1); if (arg_type == InvalidOid) ereport(ERROR, @@ -1905,7 +1889,7 @@ json_agg_transfn(PG_FUNCTION_ARGS) MemoryContextSwitchTo(oldcontext); appendStringInfoChar(state->str, '['); - json_categorize_type(arg_type,&state->val_category, + json_categorize_type(arg_type, &state->val_category, &state->val_output_func); } else @@ -1949,7 +1933,7 @@ json_agg_transfn(PG_FUNCTION_ARGS) Datum json_agg_finalfn(PG_FUNCTION_ARGS) { - JsonAggState *state; + JsonAggState *state; /* cannot be called directly because of internal-type argument */ Assert(AggCheckCallContext(fcinfo, NULL)); @@ -1976,7 +1960,7 @@ json_object_agg_transfn(PG_FUNCTION_ARGS) { MemoryContext aggcontext, oldcontext; - JsonAggState *state; + JsonAggState *state; Datum arg; if (!AggCheckCallContext(fcinfo, &aggcontext)) @@ -2007,7 +1991,7 @@ json_object_agg_transfn(PG_FUNCTION_ARGS) (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("could not determine data type for argument 1"))); - json_categorize_type(arg_type,&state->key_category, + json_categorize_type(arg_type, &state->key_category, &state->key_output_func); arg_type = get_fn_expr_argtype(fcinfo->flinfo, 2); @@ -2017,7 +2001,7 @@ json_object_agg_transfn(PG_FUNCTION_ARGS) (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("could not determine data type for argument 2"))); - json_categorize_type(arg_type,&state->val_category, + json_categorize_type(arg_type, &state->val_category, &state->val_output_func); appendStringInfoString(state->str, "{ "); @@ -2065,7 +2049,7 @@ json_object_agg_transfn(PG_FUNCTION_ARGS) Datum json_object_agg_finalfn(PG_FUNCTION_ARGS) { - JsonAggState *state; + JsonAggState *state; /* cannot be called directly because of internal-type argument */ Assert(AggCheckCallContext(fcinfo, NULL)); diff --git a/src/backend/utils/adt/jsonb.c b/src/backend/utils/adt/jsonb.c index aa156c432c..d7b90b6108 100644 --- a/src/backend/utils/adt/jsonb.c +++ b/src/backend/utils/adt/jsonb.c @@ -28,14 +28,6 @@ #include "utils/syscache.h" #include "utils/typcache.h" -/* - * String to output for infinite dates and timestamps. - * Note the we don't use embedded quotes, unlike for json, because - * we store jsonb strings dequoted. - */ - -#define DT_INFINITY "infinity" - typedef struct JsonbInState { JsonbParseState *parseState; @@ -798,21 +790,18 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result, char buf[MAXDATELEN + 1]; date = DatumGetDateADT(val); - jb.type = jbvString; - + /* Same as date_out(), but forcing DateStyle */ if (DATE_NOT_FINITE(date)) - { - jb.val.string.len = strlen(DT_INFINITY); - jb.val.string.val = pstrdup(DT_INFINITY); - } + EncodeSpecialDate(date, buf); else { j2date(date + POSTGRES_EPOCH_JDATE, &(tm.tm_year), &(tm.tm_mon), &(tm.tm_mday)); EncodeDateOnly(&tm, USE_XSD_DATES, buf); - jb.val.string.len = strlen(buf); - jb.val.string.val = pstrdup(buf); } + jb.type = jbvString; + jb.val.string.len = strlen(buf); + jb.val.string.val = pstrdup(buf); } break; case JSONBTYPE_TIMESTAMP: @@ -823,24 +812,18 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result, char buf[MAXDATELEN + 1]; timestamp = DatumGetTimestamp(val); - jb.type = jbvString; - + /* Same as timestamp_out(), but forcing DateStyle */ if (TIMESTAMP_NOT_FINITE(timestamp)) - { - jb.val.string.len = strlen(DT_INFINITY); - jb.val.string.val = pstrdup(DT_INFINITY); - } + EncodeSpecialTimestamp(timestamp, buf); else if (timestamp2tm(timestamp, NULL, &tm, &fsec, NULL, NULL) == 0) - { - EncodeDateTime(&tm, fsec, false, 0, NULL, USE_XSD_DATES, buf); - jb.val.string.len = strlen(buf); - jb.val.string.val = pstrdup(buf); - } else ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); + jb.type = jbvString; + jb.val.string.len = strlen(buf); + jb.val.string.val = pstrdup(buf); } break; case JSONBTYPE_TIMESTAMPTZ: @@ -852,24 +835,19 @@ datum_to_jsonb(Datum val, bool is_null, JsonbInState *result, const char *tzn = NULL; char buf[MAXDATELEN + 1]; - timestamp = DatumGetTimestamp(val); - jb.type = jbvString; - + timestamp = DatumGetTimestampTz(val); + /* Same as timestamptz_out(), but forcing DateStyle */ if (TIMESTAMP_NOT_FINITE(timestamp)) - { - jb.val.string.len = strlen(DT_INFINITY); - jb.val.string.val = pstrdup(DT_INFINITY); - } + EncodeSpecialTimestamp(timestamp, buf); else if (timestamp2tm(timestamp, &tz, &tm, &fsec, &tzn, NULL) == 0) - { EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf); - jb.val.string.len = strlen(buf); - jb.val.string.val = pstrdup(buf); - } else ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); + jb.type = jbvString; + jb.val.string.len = strlen(buf); + jb.val.string.val = pstrdup(buf); } break; case JSONBTYPE_JSONCAST: diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index 0c39a1ac02..8fbb310f33 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -68,7 +68,6 @@ typedef struct static TimeOffset time2t(const int hour, const int min, const int sec, const fsec_t fsec); -static void EncodeSpecialTimestamp(Timestamp dt, char *str); static Timestamp dt2local(Timestamp dt, int timezone); static void AdjustTimestampForTypmod(Timestamp *time, int32 typmod); static void AdjustIntervalForTypmod(Interval *interval, int32 typmod); @@ -1500,7 +1499,7 @@ make_interval(PG_FUNCTION_ARGS) /* EncodeSpecialTimestamp() * Convert reserved timestamp data type to string. */ -static void +void EncodeSpecialTimestamp(Timestamp dt, char *str) { if (TIMESTAMP_IS_NOBEGIN(dt)) diff --git a/src/include/utils/date.h b/src/include/utils/date.h index c076fb846a..dae10910a9 100644 --- a/src/include/utils/date.h +++ b/src/include/utils/date.h @@ -90,6 +90,7 @@ typedef struct /* date.c */ extern double date2timestamp_no_overflow(DateADT dateVal); +extern void EncodeSpecialDate(DateADT dt, char *str); extern Datum date_in(PG_FUNCTION_ARGS); extern Datum date_out(PG_FUNCTION_ARGS); diff --git a/src/include/utils/datetime.h b/src/include/utils/datetime.h index 5b86ca10ef..e9a1ecebeb 100644 --- a/src/include/utils/datetime.h +++ b/src/include/utils/datetime.h @@ -326,6 +326,7 @@ extern void EncodeDateOnly(struct pg_tm * tm, int style, char *str); extern void EncodeTimeOnly(struct pg_tm * tm, fsec_t fsec, bool print_tz, int tz, int style, char *str); extern void EncodeDateTime(struct pg_tm * tm, fsec_t fsec, bool print_tz, int tz, const char *tzn, int style, char *str); extern void EncodeInterval(struct pg_tm * tm, fsec_t fsec, int style, char *str); +extern void EncodeSpecialTimestamp(Timestamp dt, char *str); extern int ValidateDate(int fmask, bool isjulian, bool is2digits, bool bc, struct pg_tm * tm); diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out index 42a7c7109f..0ced17e130 100644 --- a/src/test/regress/expected/json.out +++ b/src/test/regress/expected/json.out @@ -418,18 +418,36 @@ select to_json(date 'Infinity'); "infinity" (1 row) +select to_json(date '-Infinity'); + to_json +------------- + "-infinity" +(1 row) + select to_json(timestamp 'Infinity'); to_json ------------ "infinity" (1 row) +select to_json(timestamp '-Infinity'); + to_json +------------- + "-infinity" +(1 row) + select to_json(timestamptz 'Infinity'); to_json ------------ "infinity" (1 row) +select to_json(timestamptz '-Infinity'); + to_json +------------- + "-infinity" +(1 row) + --json_agg SELECT json_agg(q) FROM ( SELECT $$a$$ || x AS b, y AS c, diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out index bee95a3a9f..bd06a0e49a 100644 --- a/src/test/regress/expected/jsonb.out +++ b/src/test/regress/expected/jsonb.out @@ -314,18 +314,36 @@ select to_jsonb(date 'Infinity'); "infinity" (1 row) +select to_jsonb(date '-Infinity'); + to_jsonb +------------- + "-infinity" +(1 row) + select to_jsonb(timestamp 'Infinity'); to_jsonb ------------ "infinity" (1 row) +select to_jsonb(timestamp '-Infinity'); + to_jsonb +------------- + "-infinity" +(1 row) + select to_jsonb(timestamptz 'Infinity'); to_jsonb ------------ "infinity" (1 row) +select to_jsonb(timestamptz '-Infinity'); + to_jsonb +------------- + "-infinity" +(1 row) + --jsonb_agg CREATE TEMP TABLE rows AS SELECT x, 'txt' || x as y diff --git a/src/test/regress/sql/json.sql b/src/test/regress/sql/json.sql index 7803886338..0d2c139b4d 100644 --- a/src/test/regress/sql/json.sql +++ b/src/test/regress/sql/json.sql @@ -116,8 +116,11 @@ COMMIT; select to_json(date '2014-05-28'); select to_json(date 'Infinity'); +select to_json(date '-Infinity'); select to_json(timestamp 'Infinity'); +select to_json(timestamp '-Infinity'); select to_json(timestamptz 'Infinity'); +select to_json(timestamptz '-Infinity'); --json_agg diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql index d93b8c3de8..a6b6d48205 100644 --- a/src/test/regress/sql/jsonb.sql +++ b/src/test/regress/sql/jsonb.sql @@ -76,8 +76,11 @@ COMMIT; select to_jsonb(date '2014-05-28'); select to_jsonb(date 'Infinity'); +select to_jsonb(date '-Infinity'); select to_jsonb(timestamp 'Infinity'); +select to_jsonb(timestamp '-Infinity'); select to_jsonb(timestamptz 'Infinity'); +select to_jsonb(timestamptz '-Infinity'); --jsonb_agg