diff options
Diffstat (limited to 'src/backend/utils')
-rw-r--r-- | src/backend/utils/adt/jsonpath.c | 138 | ||||
-rw-r--r-- | src/backend/utils/adt/jsonpath_exec.c | 718 | ||||
-rw-r--r-- | src/backend/utils/adt/jsonpath_gram.y | 78 | ||||
-rw-r--r-- | src/backend/utils/adt/jsonpath_scan.l | 11 |
4 files changed, 929 insertions, 16 deletions
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c index d02c03e014d..258ed8eb117 100644 --- a/src/backend/utils/adt/jsonpath.c +++ b/src/backend/utils/adt/jsonpath.c @@ -295,6 +295,7 @@ flattenJsonPathParseItem(StringInfo buf, int *result, struct Node *escontext, case jpiDiv: case jpiMod: case jpiStartsWith: + case jpiDecimal: { /* * First, reserve place for left/right arg's positions, then @@ -355,6 +356,10 @@ flattenJsonPathParseItem(StringInfo buf, int *result, struct Node *escontext, case jpiMinus: case jpiExists: case jpiDatetime: + case jpiTime: + case jpiTimeTz: + case jpiTimestamp: + case jpiTimestampTz: { int32 arg = reserveSpaceForItemPointer(buf); @@ -444,6 +449,12 @@ flattenJsonPathParseItem(StringInfo buf, int *result, struct Node *escontext, case jpiCeiling: case jpiDouble: case jpiKeyValue: + case jpiBigint: + case jpiBoolean: + case jpiDate: + case jpiInteger: + case jpiNumber: + case jpiStringFunc: break; default: elog(ERROR, "unrecognized jsonpath item type: %d", item->type); @@ -742,6 +753,75 @@ printJsonPathItem(StringInfo buf, JsonPathItem *v, bool inKey, if (printBracketes) appendStringInfoChar(buf, ')'); break; + case jpiBigint: + appendStringInfoString(buf, ".bigint()"); + break; + case jpiBoolean: + appendStringInfoString(buf, ".boolean()"); + break; + case jpiDate: + appendStringInfoString(buf, ".date()"); + break; + case jpiDecimal: + appendStringInfoString(buf, ".decimal("); + if (v->content.args.left) + { + jspGetLeftArg(v, &elem); + printJsonPathItem(buf, &elem, false, false); + } + if (v->content.args.right) + { + appendStringInfoChar(buf, ','); + jspGetRightArg(v, &elem); + printJsonPathItem(buf, &elem, false, false); + } + appendStringInfoChar(buf, ')'); + break; + case jpiInteger: + appendStringInfoString(buf, ".integer()"); + break; + case jpiNumber: + appendStringInfoString(buf, ".number()"); + break; + case jpiStringFunc: + appendStringInfoString(buf, ".string()"); + break; + case jpiTime: + appendStringInfoString(buf, ".time("); + if (v->content.arg) + { + jspGetArg(v, &elem); + printJsonPathItem(buf, &elem, false, false); + } + appendStringInfoChar(buf, ')'); + break; + case jpiTimeTz: + appendStringInfoString(buf, ".time_tz("); + if (v->content.arg) + { + jspGetArg(v, &elem); + printJsonPathItem(buf, &elem, false, false); + } + appendStringInfoChar(buf, ')'); + break; + case jpiTimestamp: + appendStringInfoString(buf, ".timestamp("); + if (v->content.arg) + { + jspGetArg(v, &elem); + printJsonPathItem(buf, &elem, false, false); + } + appendStringInfoChar(buf, ')'); + break; + case jpiTimestampTz: + appendStringInfoString(buf, ".timestamp_tz("); + if (v->content.arg) + { + jspGetArg(v, &elem); + printJsonPathItem(buf, &elem, false, false); + } + appendStringInfoChar(buf, ')'); + break; default: elog(ERROR, "unrecognized jsonpath item type: %d", v->type); } @@ -803,6 +883,28 @@ jspOperationName(JsonPathItemType type) return "starts with"; case jpiLikeRegex: return "like_regex"; + case jpiBigint: + return "bigint"; + case jpiBoolean: + return "boolean"; + case jpiDate: + return "date"; + case jpiDecimal: + return "decimal"; + case jpiInteger: + return "integer"; + case jpiNumber: + return "number"; + case jpiStringFunc: + return "string"; + case jpiTime: + return "time"; + case jpiTimeTz: + return "time_tz"; + case jpiTimestamp: + return "timestamp"; + case jpiTimestampTz: + return "timestamp_tz"; default: elog(ERROR, "unrecognized jsonpath item type: %d", type); return NULL; @@ -899,6 +1001,12 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) case jpiDouble: case jpiKeyValue: case jpiLast: + case jpiBigint: + case jpiBoolean: + case jpiDate: + case jpiInteger: + case jpiNumber: + case jpiStringFunc: break; case jpiString: case jpiKey: @@ -923,6 +1031,7 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) case jpiDiv: case jpiMod: case jpiStartsWith: + case jpiDecimal: read_int32(v->content.args.left, base, pos); read_int32(v->content.args.right, base, pos); break; @@ -933,6 +1042,10 @@ jspInitByBuffer(JsonPathItem *v, char *base, int32 pos) case jpiMinus: case jpiFilter: case jpiDatetime: + case jpiTime: + case jpiTimeTz: + case jpiTimestamp: + case jpiTimestampTz: read_int32(v->content.arg, base, pos); break; case jpiIndexArray: @@ -964,7 +1077,11 @@ jspGetArg(JsonPathItem *v, JsonPathItem *a) v->type == jpiMinus || v->type == jpiFilter || v->type == jpiExists || - v->type == jpiDatetime); + v->type == jpiDatetime || + v->type == jpiTime || + v->type == jpiTimeTz || + v->type == jpiTimestamp || + v->type == jpiTimestampTz); jspInitByBuffer(a, v->base, v->content.arg); } @@ -1015,7 +1132,18 @@ jspGetNext(JsonPathItem *v, JsonPathItem *a) v->type == jpiKeyValue || v->type == jpiLast || v->type == jpiStartsWith || - v->type == jpiLikeRegex); + v->type == jpiLikeRegex || + v->type == jpiBigint || + v->type == jpiBoolean || + v->type == jpiDate || + v->type == jpiDecimal || + v->type == jpiInteger || + v->type == jpiNumber || + v->type == jpiStringFunc || + v->type == jpiTime || + v->type == jpiTimeTz || + v->type == jpiTimestamp || + v->type == jpiTimestampTz); if (a) jspInitByBuffer(a, v->base, v->nextPos); @@ -1041,7 +1169,8 @@ jspGetLeftArg(JsonPathItem *v, JsonPathItem *a) v->type == jpiMul || v->type == jpiDiv || v->type == jpiMod || - v->type == jpiStartsWith); + v->type == jpiStartsWith || + v->type == jpiDecimal); jspInitByBuffer(a, v->base, v->content.args.left); } @@ -1062,7 +1191,8 @@ jspGetRightArg(JsonPathItem *v, JsonPathItem *a) v->type == jpiMul || v->type == jpiDiv || v->type == jpiMod || - v->type == jpiStartsWith); + v->type == jpiStartsWith || + v->type == jpiDecimal); jspInitByBuffer(a, v->base, v->content.args.right); } diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c index cb2ea048c35..bf37f0195c8 100644 --- a/src/backend/utils/adt/jsonpath_exec.c +++ b/src/backend/utils/adt/jsonpath_exec.c @@ -1094,6 +1094,11 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, break; case jpiDatetime: + case jpiDate: + case jpiTime: + case jpiTimeTz: + case jpiTimestamp: + case jpiTimestampTz: if (unwrap && JsonbType(jb) == jbvArray) return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false); @@ -1133,6 +1138,420 @@ executeItemOptUnwrapTarget(JsonPathExecContext *cxt, JsonPathItem *jsp, } break; + case jpiBigint: + { + JsonbValue jbv; + Datum datum; + + if (unwrap && JsonbType(jb) == jbvArray) + return executeItemUnwrapTargetArray(cxt, jsp, jb, found, + false); + + if (jb->type == jbvNumeric) + { + bool have_error; + int64 val; + + val = numeric_int8_opt_error(jb->val.numeric, &have_error); + if (have_error) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM), + errmsg("numeric argument of jsonpath item method .%s() is out of range for type bigint", + jspOperationName(jsp->type))))); + + datum = Int64GetDatum(val); + res = jperOk; + } + else if (jb->type == jbvString) + { + /* cast string as bigint */ + char *tmp = pnstrdup(jb->val.string.val, + jb->val.string.len); + ErrorSaveContext escontext = {T_ErrorSaveContext}; + bool noerr; + + noerr = DirectInputFunctionCallSafe(int8in, tmp, + InvalidOid, -1, + (Node *) &escontext, + &datum); + + if (!noerr || escontext.error_occurred) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM), + errmsg("string argument of jsonpath item method .%s() is not a valid representation of a big integer", + jspOperationName(jsp->type))))); + res = jperOk; + } + + if (res == jperNotFound) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM), + errmsg("jsonpath item method .%s() can only be applied to a string or numeric value", + jspOperationName(jsp->type))))); + + jb = &jbv; + jb->type = jbvNumeric; + jb->val.numeric = DatumGetNumeric(DirectFunctionCall1(int8_numeric, + datum)); + + res = executeNextItem(cxt, jsp, NULL, jb, found, true); + } + break; + + case jpiBoolean: + { + JsonbValue jbv; + bool bval; + + if (unwrap && JsonbType(jb) == jbvArray) + return executeItemUnwrapTargetArray(cxt, jsp, jb, found, + false); + + if (jb->type == jbvBool) + { + bval = jb->val.boolean; + + res = jperOk; + } + else if (jb->type == jbvNumeric) + { + int ival; + Datum datum; + bool noerr; + char *tmp = DatumGetCString(DirectFunctionCall1(numeric_out, + NumericGetDatum(jb->val.numeric))); + ErrorSaveContext escontext = {T_ErrorSaveContext}; + + noerr = DirectInputFunctionCallSafe(int4in, tmp, + InvalidOid, -1, + (Node *) &escontext, + &datum); + + if (!noerr || escontext.error_occurred) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM), + errmsg("numeric argument of jsonpath item method .%s() is out of range for type boolean", + jspOperationName(jsp->type))))); + + ival = DatumGetInt32(datum); + if (ival == 0) + bval = false; + else + bval = true; + + res = jperOk; + } + else if (jb->type == jbvString) + { + /* cast string as boolean */ + char *tmp = pnstrdup(jb->val.string.val, + jb->val.string.len); + + if (!parse_bool(tmp, &bval)) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM), + errmsg("string argument of jsonpath item method .%s() is not a valid representation of a boolean", + jspOperationName(jsp->type))))); + + res = jperOk; + } + + if (res == jperNotFound) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM), + errmsg("jsonpath item method .%s() can only be applied to a bool, string, or numeric value", + jspOperationName(jsp->type))))); + + jb = &jbv; + jb->type = jbvBool; + jb->val.boolean = bval; + + res = executeNextItem(cxt, jsp, NULL, jb, found, true); + } + break; + + case jpiDecimal: + case jpiNumber: + { + JsonbValue jbv; + Numeric num; + char *numstr = NULL; + + if (unwrap && JsonbType(jb) == jbvArray) + return executeItemUnwrapTargetArray(cxt, jsp, jb, found, + false); + + if (jb->type == jbvNumeric) + { + num = jb->val.numeric; + if (numeric_is_nan(num) || numeric_is_inf(num)) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM), + errmsg("numeric argument of jsonpath item method .%s() is out of range for type decimal or number", + jspOperationName(jsp->type))))); + + if (jsp->type == jpiDecimal) + numstr = DatumGetCString(DirectFunctionCall1(numeric_out, + NumericGetDatum(num))); + res = jperOk; + } + else if (jb->type == jbvString) + { + /* cast string as number */ + Datum datum; + bool noerr; + ErrorSaveContext escontext = {T_ErrorSaveContext}; + + numstr = pnstrdup(jb->val.string.val, jb->val.string.len); + + noerr = DirectInputFunctionCallSafe(numeric_in, numstr, + InvalidOid, -1, + (Node *) &escontext, + &datum); + + if (!noerr || escontext.error_occurred) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM), + errmsg("string argument of jsonpath item method .%s() is not a valid representation of a decimal or number", + jspOperationName(jsp->type))))); + + num = DatumGetNumeric(datum); + if (numeric_is_nan(num) || numeric_is_inf(num)) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM), + errmsg("string argument of jsonpath item method .%s() is not a valid representation of a decimal or number", + jspOperationName(jsp->type))))); + + res = jperOk; + } + + if (res == jperNotFound) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM), + errmsg("jsonpath item method .%s() can only be applied to a string or numeric value", + jspOperationName(jsp->type))))); + + /* + * If we have arguments, then they must be the precision and + * optional scale used in .decimal(). Convert them to the + * typmod equivalent and then truncate the numeric value per + * this typmod details. + */ + if (jsp->type == jpiDecimal && jsp->content.args.left) + { + Datum numdatum; + Datum dtypmod; + int32 precision; + int32 scale = 0; + bool have_error; + bool noerr; + ArrayType *arrtypmod; + Datum datums[2]; + char pstr[12]; /* sign, 10 digits and '\0' */ + char sstr[12]; /* sign, 10 digits and '\0' */ + ErrorSaveContext escontext = {T_ErrorSaveContext}; + + jspGetLeftArg(jsp, &elem); + if (elem.type != jpiNumeric) + elog(ERROR, "invalid jsonpath item type for .decimal() precision"); + + precision = numeric_int4_opt_error(jspGetNumeric(&elem), + &have_error); + if (have_error) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM), + errmsg("precision of jsonpath item method .%s() is out of range for type integer", + jspOperationName(jsp->type))))); + + if (jsp->content.args.right) + { + jspGetRightArg(jsp, &elem); + if (elem.type != jpiNumeric) + elog(ERROR, "invalid jsonpath item type for .decimal() scale"); + + scale = numeric_int4_opt_error(jspGetNumeric(&elem), + &have_error); + if (have_error) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM), + errmsg("scale of jsonpath item method .%s() is out of range for type integer", + jspOperationName(jsp->type))))); + } + + /* + * numerictypmodin() takes the precision and scale in the + * form of CString arrays. + */ + pg_ltoa(precision, pstr); + datums[0] = CStringGetDatum(pstr); + pg_ltoa(scale, sstr); + datums[1] = CStringGetDatum(sstr); + arrtypmod = construct_array_builtin(datums, 2, CSTRINGOID); + + dtypmod = DirectFunctionCall1(numerictypmodin, + PointerGetDatum(arrtypmod)); + + /* Convert numstr to Numeric with typmod */ + Assert(numstr != NULL); + noerr = DirectInputFunctionCallSafe(numeric_in, numstr, + InvalidOid, dtypmod, + (Node *) &escontext, + &numdatum); + + if (!noerr || escontext.error_occurred) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM), + errmsg("string argument of jsonpath item method .%s() is not a valid representation of a decimal or number", + jspOperationName(jsp->type))))); + + num = DatumGetNumeric(numdatum); + pfree(arrtypmod); + } + + jb = &jbv; + jb->type = jbvNumeric; + jb->val.numeric = num; + + res = executeNextItem(cxt, jsp, NULL, jb, found, true); + } + break; + + case jpiInteger: + { + JsonbValue jbv; + Datum datum; + + if (unwrap && JsonbType(jb) == jbvArray) + return executeItemUnwrapTargetArray(cxt, jsp, jb, found, + false); + + if (jb->type == jbvNumeric) + { + bool have_error; + int32 val; + + val = numeric_int4_opt_error(jb->val.numeric, &have_error); + if (have_error) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM), + errmsg("numeric argument of jsonpath item method .%s() is out of range for type integer", + jspOperationName(jsp->type))))); + + datum = Int32GetDatum(val); + res = jperOk; + } + else if (jb->type == jbvString) + { + /* cast string as integer */ + char *tmp = pnstrdup(jb->val.string.val, + jb->val.string.len); + ErrorSaveContext escontext = {T_ErrorSaveContext}; + bool noerr; + + noerr = DirectInputFunctionCallSafe(int4in, tmp, + InvalidOid, -1, + (Node *) &escontext, + &datum); + + if (!noerr || escontext.error_occurred) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM), + errmsg("string argument of jsonpath item method .%s() is not a valid representation of an integer", + jspOperationName(jsp->type))))); + res = jperOk; + } + + if (res == jperNotFound) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM), + errmsg("jsonpath item method .%s() can only be applied to a string or numeric value", + jspOperationName(jsp->type))))); + + jb = &jbv; + jb->type = jbvNumeric; + jb->val.numeric = DatumGetNumeric(DirectFunctionCall1(int4_numeric, + datum)); + + res = executeNextItem(cxt, jsp, NULL, jb, found, true); + } + break; + + case jpiStringFunc: + { + JsonbValue jbv; + char *tmp = NULL; + + switch (JsonbType(jb)) + { + case jbvString: + + /* + * Value is not necessarily null-terminated, so we do + * pnstrdup() here. + */ + tmp = pnstrdup(jb->val.string.val, + jb->val.string.len); + break; + case jbvNumeric: + tmp = DatumGetCString(DirectFunctionCall1(numeric_out, + NumericGetDatum(jb->val.numeric))); + break; + case jbvBool: + tmp = (jb->val.boolean) ? "true" : "false"; + break; + case jbvDatetime: + { + switch (jb->val.datetime.typid) + { + case DATEOID: + tmp = DatumGetCString(DirectFunctionCall1(date_out, + jb->val.datetime.value)); + break; + case TIMEOID: + tmp = DatumGetCString(DirectFunctionCall1(time_out, + jb->val.datetime.value)); + break; + case TIMETZOID: + tmp = DatumGetCString(DirectFunctionCall1(timetz_out, + jb->val.datetime.value)); + break; + case TIMESTAMPOID: + tmp = DatumGetCString(DirectFunctionCall1(timestamp_out, + jb->val.datetime.value)); + break; + case TIMESTAMPTZOID: + tmp = DatumGetCString(DirectFunctionCall1(timestamptz_out, + jb->val.datetime.value)); + break; + default: + elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u", + jb->val.datetime.typid); + } + } + break; + case jbvNull: + case jbvArray: + case jbvObject: + case jbvBinary: + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM), + errmsg("jsonpath item method .%s() can only be applied to a bool, string, numeric, or datetime value", + jspOperationName(jsp->type))))); + break; + } + + res = jperOk; + + jb = &jbv; + Assert(tmp != NULL); /* We must have set tmp above */ + jb->val.string.val = (jb->type == jbvString) ? tmp : pstrdup(tmp); + jb->val.string.len = strlen(jb->val.string.val); + jb->type = jbvString; + + res = executeNextItem(cxt, jsp, NULL, jb, found, true); + } + break; + default: elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type); } @@ -1794,11 +2213,16 @@ executeNumericItemMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, } /* - * Implementation of the .datetime() method. + * Implementation of the .datetime() and related methods. * - * Converts a string into a date/time value. The actual type is determined at run time. + * Converts a string into a date/time value. The actual type is determined at + * run time. * If an argument is provided, this argument is used as a template string. * Otherwise, the first fitting ISO format is selected. + * + * .date(), .time(), .time_tz(), .timestamp(), .timestamp_tz() methods don't + * have a format, so ISO format is used. However, except for .date(), they all + * take an optional time precision. */ static JsonPathExecResult executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, @@ -1814,6 +2238,7 @@ executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, bool hasNext; JsonPathExecResult res = jperNotFound; JsonPathItem elem; + int32 time_precision = -1; if (!(jb = getScalar(jb, jbvString))) RETURN_ERROR(ereport(ERROR, @@ -1831,7 +2256,11 @@ executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, */ collid = DEFAULT_COLLATION_OID; - if (jsp->content.arg) + /* + * .datetime(template) has an argument, the rest of the methods don't have + * an argument. So we handle that separately. + */ + if (jsp->type == jpiDatetime && jsp->content.arg) { text *template; char *template_str; @@ -1893,6 +2322,30 @@ executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, static text *fmt_txt[lengthof(fmt_str)] = {0}; int i; + /* + * Check for optional precision for methods other than .datetime() and + * .date() + */ + if (jsp->type != jpiDatetime && jsp->type != jpiDate && + jsp->content.arg) + { + bool have_error; + + jspGetArg(jsp, &elem); + + if (elem.type != jpiNumeric) + elog(ERROR, "invalid jsonpath item type for %s argument", + jspOperationName(jsp->type)); + + time_precision = numeric_int4_opt_error(jspGetNumeric(&elem), + &have_error); + if (have_error) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION), + errmsg("time precision of jsonpath item method .%s() is out of range for type integer", + jspOperationName(jsp->type))))); + } + /* loop until datetime format fits */ for (i = 0; i < lengthof(fmt_str); i++) { @@ -1919,11 +2372,260 @@ executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp, } if (res == jperNotFound) - RETURN_ERROR(ereport(ERROR, - (errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION), - errmsg("datetime format is not recognized: \"%s\"", - text_to_cstring(datetime)), - errhint("Use a datetime template argument to specify the input data format.")))); + { + if (jsp->type == jpiDatetime) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION), + errmsg("datetime format is not recognized: \"%s\"", + text_to_cstring(datetime)), + errhint("Use a datetime template argument to specify the input data format.")))); + else + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION), + errmsg("%s format is not recognized: \"%s\"", + jspOperationName(jsp->type), text_to_cstring(datetime))))); + + } + } + + /* + * parse_datetime() processes the entire input string per the template or + * ISO format and returns the Datum in best fitted datetime type. So, if + * this call is for a specific datatype, then we do the conversion here. + * Throw an error for incompatible types. + */ + switch (jsp->type) + { + case jpiDatetime: /* Nothing to do for DATETIME */ + break; + case jpiDate: + { + /* Convert result type to date */ + switch (typid) + { + case DATEOID: /* Nothing to do for DATE */ + break; + case TIMEOID: + case TIMETZOID: + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION), + errmsg("date format is not recognized: \"%s\"", + text_to_cstring(datetime))))); + break; + case TIMESTAMPOID: + value = DirectFunctionCall1(timestamp_date, + value); + break; + case TIMESTAMPTZOID: + value = DirectFunctionCall1(timestamptz_date, + value); + break; + default: + elog(ERROR, "type with oid %d not supported", typid); + } + + typid = DATEOID; + } + break; + case jpiTime: + { + /* Convert result type to time without time zone */ + switch (typid) + { + case DATEOID: + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION), + errmsg("time format is not recognized: \"%s\"", + text_to_cstring(datetime))))); + break; + case TIMEOID: /* Nothing to do for TIME */ + break; + case TIMETZOID: + value = DirectFunctionCall1(timetz_time, + value); + break; + case TIMESTAMPOID: + value = DirectFunctionCall1(timestamp_time, + value); + break; + case TIMESTAMPTZOID: + value = DirectFunctionCall1(timestamptz_time, + value); + break; + default: + elog(ERROR, "type with oid %d not supported", typid); + } + + /* Force the user-given time precision, if any */ + if (time_precision != -1) + { + TimeADT result; + + /* Get a warning when precision is reduced */ + time_precision = anytime_typmod_check(false, + time_precision); + result = DatumGetTimeADT(value); + AdjustTimeForTypmod(&result, time_precision); + value = TimeADTGetDatum(result); + + /* Update the typmod value with the user-given precision */ + typmod = time_precision; + } + + typid = TIMEOID; + } + break; + case jpiTimeTz: + { + /* Convert result type to time with time zone */ + switch (typid) + { + case DATEOID: + case TIMESTAMPOID: + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION), + errmsg("time_tz format is not recognized: \"%s\"", + text_to_cstring(datetime))))); + break; + case TIMEOID: + value = DirectFunctionCall1(time_timetz, + value); + break; + case TIMETZOID: /* Nothing to do for TIMETZ */ + break; + case TIMESTAMPTZOID: + value = DirectFunctionCall1(timestamptz_timetz, + value); + break; + default: + elog(ERROR, "type with oid %d not supported", typid); + } + + /* Force the user-given time precision, if any */ + if (time_precision != -1) + { + TimeTzADT *result; + + /* Get a warning when precision is reduced */ + time_precision = anytime_typmod_check(true, + time_precision); + result = DatumGetTimeTzADTP(value); + AdjustTimeForTypmod(&result->time, time_precision); + value = TimeTzADTPGetDatum(result); + + /* Update the typmod value with the user-given precision */ + typmod = time_precision; + } + + typid = TIMETZOID; + } + break; + case jpiTimestamp: + { + /* Convert result type to timestamp without time zone */ + switch (typid) + { + case DATEOID: + value = DirectFunctionCall1(date_timestamp, + value); + break; + case TIMEOID: + case TIMETZOID: + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION), + errmsg("timestamp format is not recognized: \"%s\"", + text_to_cstring(datetime))))); + break; + case TIMESTAMPOID: /* Nothing to do for TIMESTAMP */ + break; + case TIMESTAMPTZOID: + value = DirectFunctionCall1(timestamptz_timestamp, + value); + break; + default: + elog(ERROR, "type with oid %d not supported", typid); + } + + /* Force the user-given time precision, if any */ + if (time_precision != -1) + { + Timestamp result; + ErrorSaveContext escontext = {T_ErrorSaveContext}; + + /* Get a warning when precision is reduced */ + time_precision = anytimestamp_typmod_check(false, + time_precision); + result = DatumGetTimestamp(value); + AdjustTimestampForTypmod(&result, time_precision, + (Node *) &escontext); + if (escontext.error_occurred) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION), + errmsg("numeric argument of jsonpath item method .%s() is out of range for type integer", + jspOperationName(jsp->type))))); + value = TimestampGetDatum(result); + + /* Update the typmod value with the user-given precision */ + typmod = time_precision; + } + + typid = TIMESTAMPOID; + } + break; + case jpiTimestampTz: + { + /* Convert result type to timestamp with time zone */ + switch (typid) + { + case DATEOID: + value = DirectFunctionCall1(date_timestamptz, + value); + break; + case TIMEOID: + case TIMETZOID: + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION), + errmsg("timestamp_tz format is not recognized: \"%s\"", + text_to_cstring(datetime))))); + break; + case TIMESTAMPOID: + value = DirectFunctionCall1(timestamp_timestamptz, + value); + break; + case TIMESTAMPTZOID: /* Nothing to do for TIMESTAMPTZ */ + break; + default: + elog(ERROR, "type with oid %d not supported", typid); + } + + /* Force the user-given time precision, if any */ + if (time_precision != -1) + { + Timestamp result; + ErrorSaveContext escontext = {T_ErrorSaveContext}; + + /* Get a warning when precision is reduced */ + time_precision = anytimestamp_typmod_check(true, + time_precision); + result = DatumGetTimestampTz(value); + AdjustTimestampForTypmod(&result, time_precision, + (Node *) &escontext); + if (escontext.error_occurred) + RETURN_ERROR(ereport(ERROR, + (errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION), + errmsg("numeric argument of jsonpath item method .%s() is out of range for type integer", + jspOperationName(jsp->type))))); + value = TimestampTzGetDatum(result); + + /* Update the typmod value with the user-given precision */ + typmod = time_precision; + } + + typid = TIMESTAMPTZOID; + } + break; + default: + elog(ERROR, "unrecognized jsonpath item type: %d", jsp->type); } pfree(datetime); diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y index 5e4eb52641d..8733a0eac66 100644 --- a/src/backend/utils/adt/jsonpath_gram.y +++ b/src/backend/utils/adt/jsonpath_gram.y @@ -82,15 +82,18 @@ static bool makeItemLikeRegex(JsonPathParseItem *expr, %token <str> ANY_P STRICT_P LAX_P LAST_P STARTS_P WITH_P LIKE_REGEX_P FLAG_P %token <str> ABS_P SIZE_P TYPE_P FLOOR_P DOUBLE_P CEILING_P KEYVALUE_P %token <str> DATETIME_P +%token <str> BIGINT_P BOOLEAN_P DATE_P DECIMAL_P INTEGER_P NUMBER_P +%token <str> STRINGFUNC_P TIME_P TIME_TZ_P TIMESTAMP_P TIMESTAMP_TZ_P %type <result> result %type <value> scalar_value path_primary expr array_accessor any_path accessor_op key predicate delimited_predicate index_elem starts_with_initial expr_or_predicate - datetime_template opt_datetime_template + datetime_template opt_datetime_template csv_elem + datetime_precision opt_datetime_precision -%type <elems> accessor_expr +%type <elems> accessor_expr csv_list opt_csv_list %type <indexs> index_list @@ -248,9 +251,59 @@ accessor_op: | array_accessor { $$ = $1; } | '.' any_path { $$ = $2; } | '.' method '(' ')' { $$ = makeItemType($2); } - | '.' DATETIME_P '(' opt_datetime_template ')' - { $$ = makeItemUnary(jpiDatetime, $4); } | '?' '(' predicate ')' { $$ = makeItemUnary(jpiFilter, $3); } + | '.' DECIMAL_P '(' opt_csv_list ')' + { + if (list_length($4) == 0) + $$ = makeItemBinary(jpiDecimal, NULL, NULL); + else if (list_length($4) == 1) + $$ = makeItemBinary(jpiDecimal, linitial($4), NULL); + else if (list_length($4) == 2) + $$ = makeItemBinary(jpiDecimal, linitial($4), lsecond($4)); + else + ereturn(escontext, false, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid input syntax for type %s", "jsonpath"), + errdetail(".decimal() can only have an optional precision[,scale]."))); + } + | '.' DATETIME_P '(' opt_datetime_template ')' + { $$ = makeItemUnary(jpiDatetime, $4); } + | '.' TIME_P '(' opt_datetime_precision ')' + { $$ = makeItemUnary(jpiTime, $4); } + | '.' TIME_TZ_P '(' opt_datetime_precision ')' + { $$ = makeItemUnary(jpiTimeTz, $4); } + | '.' TIMESTAMP_P '(' opt_datetime_precision ')' + { $$ = makeItemUnary(jpiTimestamp, $4); } + | '.' TIMESTAMP_TZ_P '(' opt_datetime_precision ')' + { $$ = makeItemUnary(jpiTimestampTz, $4); } + ; + +csv_elem: + INT_P + { $$ = makeItemNumeric(&$1); } + | '+' INT_P %prec UMINUS + { $$ = makeItemUnary(jpiPlus, makeItemNumeric(&$2)); } + | '-' INT_P %prec UMINUS + { $$ = makeItemUnary(jpiMinus, makeItemNumeric(&$2)); } + ; + +csv_list: + csv_elem { $$ = list_make1($1); } + | csv_list ',' csv_elem { $$ = lappend($1, $3); } + ; + +opt_csv_list: + csv_list { $$ = $1; } + | /* EMPTY */ { $$ = NULL; } + ; + +datetime_precision: + INT_P { $$ = makeItemNumeric(&$1); } + ; + +opt_datetime_precision: + datetime_precision { $$ = $1; } + | /* EMPTY */ { $$ = NULL; } ; datetime_template: @@ -291,6 +344,17 @@ key_name: | WITH_P | LIKE_REGEX_P | FLAG_P + | BIGINT_P + | BOOLEAN_P + | DATE_P + | DECIMAL_P + | INTEGER_P + | NUMBER_P + | STRINGFUNC_P + | TIME_P + | TIME_TZ_P + | TIMESTAMP_P + | TIMESTAMP_TZ_P ; method: @@ -301,6 +365,12 @@ method: | DOUBLE_P { $$ = jpiDouble; } | CEILING_P { $$ = jpiCeiling; } | KEYVALUE_P { $$ = jpiKeyValue; } + | BIGINT_P { $$ = jpiBigint; } + | BOOLEAN_P { $$ = jpiBoolean; } + | DATE_P { $$ = jpiDate; } + | INTEGER_P { $$ = jpiInteger; } + | NUMBER_P { $$ = jpiNumber; } + | STRINGFUNC_P { $$ = jpiStringFunc; } ; %% diff --git a/src/backend/utils/adt/jsonpath_scan.l b/src/backend/utils/adt/jsonpath_scan.l index 757cd953d98..7acda778375 100644 --- a/src/backend/utils/adt/jsonpath_scan.l +++ b/src/backend/utils/adt/jsonpath_scan.l @@ -401,24 +401,35 @@ static const JsonPathKeyword keywords[] = { { 2, false, TO_P, "to"}, { 3, false, ABS_P, "abs"}, { 3, false, LAX_P, "lax"}, + { 4, false, DATE_P, "date"}, { 4, false, FLAG_P, "flag"}, { 4, false, LAST_P, "last"}, { 4, true, NULL_P, "null"}, { 4, false, SIZE_P, "size"}, + { 4, false, TIME_P, "time"}, { 4, true, TRUE_P, "true"}, { 4, false, TYPE_P, "type"}, { 4, false, WITH_P, "with"}, { 5, true, FALSE_P, "false"}, { 5, false, FLOOR_P, "floor"}, + { 6, false, BIGINT_P, "bigint"}, { 6, false, DOUBLE_P, "double"}, { 6, false, EXISTS_P, "exists"}, + { 6, false, NUMBER_P, "number"}, { 6, false, STARTS_P, "starts"}, { 6, false, STRICT_P, "strict"}, + { 6, false, STRINGFUNC_P, "string"}, + { 7, false, BOOLEAN_P, "boolean"}, { 7, false, CEILING_P, "ceiling"}, + { 7, false, DECIMAL_P, "decimal"}, + { 7, false, INTEGER_P, "integer"}, + { 7, false, TIME_TZ_P, "time_tz"}, { 7, false, UNKNOWN_P, "unknown"}, { 8, false, DATETIME_P, "datetime"}, { 8, false, KEYVALUE_P, "keyvalue"}, + { 9, false, TIMESTAMP_P, "timestamp"}, { 10,false, LIKE_REGEX_P, "like_regex"}, + { 12,false, TIMESTAMP_TZ_P, "timestamp_tz"}, }; /* Check if current scanstring value is a keyword */ |