#include "miscadmin.h"
#include "regex/regex.h"
#include "utils/builtins.h"
+#include "utils/datetime.h"
#include "utils/datum.h"
#include "utils/formatting.h"
#include "utils/float.h"
#include "utils/varlena.h"
-/* Standard error message for SQL/JSON errors */
-#define ERRMSG_JSON_ARRAY_NOT_FOUND "SQL/JSON array not found"
-#define ERRMSG_JSON_OBJECT_NOT_FOUND "SQL/JSON object not found"
-#define ERRMSG_JSON_MEMBER_NOT_FOUND "SQL/JSON member not found"
-#define ERRMSG_JSON_NUMBER_NOT_FOUND "SQL/JSON number not found"
-#define ERRMSG_JSON_SCALAR_REQUIRED "SQL/JSON scalar required"
-#define ERRMSG_SINGLETON_JSON_ITEM_REQUIRED "singleton SQL/JSON item required"
-#define ERRMSG_NON_NUMERIC_JSON_ITEM "non-numeric SQL/JSON item"
-#define ERRMSG_INVALID_JSON_SUBSCRIPT "invalid SQL/JSON subscript"
-
/*
* Represents "base object" and it's "id" for .keyvalue() evaluation.
*/
* ignored */
bool throwErrors; /* with "false" all suppressible errors are
* suppressed */
+ bool useTz;
} JsonPathExecContext;
/* Context for LIKE_REGEX execution. */
typedef struct JsonValueListIterator
{
JsonbValue *value;
+ List *list;
ListCell *next;
} JsonValueListIterator;
typedef Numeric (*BinaryArithmFunc) (Numeric num1, Numeric num2, bool *error);
static JsonPathExecResult executeJsonPath(JsonPath *path, Jsonb *vars,
- Jsonb *json, bool throwErrors, JsonValueList *result);
+ Jsonb *json, bool throwErrors,
+ JsonValueList *result, bool useTz);
static JsonPathExecResult executeItem(JsonPathExecContext *cxt,
- JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
+ JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
static JsonPathExecResult executeItemOptUnwrapTarget(JsonPathExecContext *cxt,
- JsonPathItem *jsp, JsonbValue *jb,
- JsonValueList *found, bool unwrap);
+ JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *found, bool unwrap);
static JsonPathExecResult executeItemUnwrapTargetArray(JsonPathExecContext *cxt,
- JsonPathItem *jsp, JsonbValue *jb,
- JsonValueList *found, bool unwrapElements);
+ JsonPathItem *jsp, JsonbValue *jb,
+ JsonValueList *found, bool unwrapElements);
static JsonPathExecResult executeNextItem(JsonPathExecContext *cxt,
- JsonPathItem *cur, JsonPathItem *next,
- JsonbValue *v, JsonValueList *found, bool copy);
-static JsonPathExecResult executeItemOptUnwrapResult(
- JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
- bool unwrap, JsonValueList *found);
-static JsonPathExecResult executeItemOptUnwrapResultNoThrow(
- JsonPathExecContext *cxt, JsonPathItem *jsp,
- JsonbValue *jb, bool unwrap, JsonValueList *found);
+ JsonPathItem *cur, JsonPathItem *next,
+ JsonbValue *v, JsonValueList *found, bool copy);
+static JsonPathExecResult executeItemOptUnwrapResult(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
+ bool unwrap, JsonValueList *found);
+static JsonPathExecResult executeItemOptUnwrapResultNoThrow(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb, bool unwrap, JsonValueList *found);
static JsonPathBool executeBoolItem(JsonPathExecContext *cxt,
- JsonPathItem *jsp, JsonbValue *jb, bool canHaveNext);
+ JsonPathItem *jsp, JsonbValue *jb, bool canHaveNext);
static JsonPathBool executeNestedBoolItem(JsonPathExecContext *cxt,
- JsonPathItem *jsp, JsonbValue *jb);
+ JsonPathItem *jsp, JsonbValue *jb);
static JsonPathExecResult executeAnyItem(JsonPathExecContext *cxt,
- JsonPathItem *jsp, JsonbContainer *jbc, JsonValueList *found,
- uint32 level, uint32 first, uint32 last,
- bool ignoreStructuralErrors, bool unwrapNext);
+ JsonPathItem *jsp, JsonbContainer *jbc, JsonValueList *found,
+ uint32 level, uint32 first, uint32 last,
+ bool ignoreStructuralErrors, bool unwrapNext);
static JsonPathBool executePredicate(JsonPathExecContext *cxt,
- JsonPathItem *pred, JsonPathItem *larg, JsonPathItem *rarg,
- JsonbValue *jb, bool unwrapRightArg,
- JsonPathPredicateCallback exec, void *param);
+ JsonPathItem *pred, JsonPathItem *larg, JsonPathItem *rarg,
+ JsonbValue *jb, bool unwrapRightArg,
+ JsonPathPredicateCallback exec, void *param);
static JsonPathExecResult executeBinaryArithmExpr(JsonPathExecContext *cxt,
- JsonPathItem *jsp, JsonbValue *jb,
- BinaryArithmFunc func, JsonValueList *found);
+ JsonPathItem *jsp, JsonbValue *jb,
+ BinaryArithmFunc func, JsonValueList *found);
static JsonPathExecResult executeUnaryArithmExpr(JsonPathExecContext *cxt,
- JsonPathItem *jsp, JsonbValue *jb, PGFunction func,
- JsonValueList *found);
+ JsonPathItem *jsp, JsonbValue *jb, PGFunction func,
+ JsonValueList *found);
static JsonPathBool executeStartsWith(JsonPathItem *jsp,
- JsonbValue *whole, JsonbValue *initial, void *param);
+ JsonbValue *whole, JsonbValue *initial, void *param);
static JsonPathBool executeLikeRegex(JsonPathItem *jsp, JsonbValue *str,
- JsonbValue *rarg, void *param);
+ JsonbValue *rarg, void *param);
static JsonPathExecResult executeNumericItemMethod(JsonPathExecContext *cxt,
- JsonPathItem *jsp, JsonbValue *jb, bool unwrap, PGFunction func,
- JsonValueList *found);
+ JsonPathItem *jsp, JsonbValue *jb, bool unwrap, PGFunction func,
+ JsonValueList *found);
+static JsonPathExecResult executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb, JsonValueList *found);
static JsonPathExecResult executeKeyValueMethod(JsonPathExecContext *cxt,
- JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
+ JsonPathItem *jsp, JsonbValue *jb, JsonValueList *found);
static JsonPathExecResult appendBoolResult(JsonPathExecContext *cxt,
- JsonPathItem *jsp, JsonValueList *found, JsonPathBool res);
+ JsonPathItem *jsp, JsonValueList *found, JsonPathBool res);
static void getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item,
- JsonbValue *value);
+ JsonbValue *value);
static void getJsonPathVariable(JsonPathExecContext *cxt,
- JsonPathItem *variable, Jsonb *vars, JsonbValue *value);
+ JsonPathItem *variable, Jsonb *vars, JsonbValue *value);
static int JsonbArraySize(JsonbValue *jb);
static JsonPathBool executeComparison(JsonPathItem *cmp, JsonbValue *lv,
- JsonbValue *rv, void *p);
-static JsonPathBool compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2);
+ JsonbValue *rv, void *p);
+static JsonPathBool compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2,
+ bool useTz);
static int compareNumeric(Numeric a, Numeric b);
static JsonbValue *copyJsonbValue(JsonbValue *src);
static JsonPathExecResult getArrayIndex(JsonPathExecContext *cxt,
- JsonPathItem *jsp, JsonbValue *jb, int32 *index);
+ JsonPathItem *jsp, JsonbValue *jb, int32 *index);
static JsonBaseObjectInfo setBaseObject(JsonPathExecContext *cxt,
- JsonbValue *jbv, int32 id);
+ JsonbValue *jbv, int32 id);
static void JsonValueListAppend(JsonValueList *jvl, JsonbValue *jbv);
static int JsonValueListLength(const JsonValueList *jvl);
static bool JsonValueListIsEmpty(JsonValueList *jvl);
static JsonbValue *JsonValueListHead(JsonValueList *jvl);
static List *JsonValueListGetList(JsonValueList *jvl);
static void JsonValueListInitIterator(const JsonValueList *jvl,
- JsonValueListIterator *it);
+ JsonValueListIterator *it);
static JsonbValue *JsonValueListNext(const JsonValueList *jvl,
- JsonValueListIterator *it);
+ JsonValueListIterator *it);
static int JsonbType(JsonbValue *jb);
static JsonbValue *JsonbInitBinary(JsonbValue *jbv, Jsonb *jb);
static int JsonbType(JsonbValue *jb);
static JsonbValue *getScalar(JsonbValue *scalar, enum jbvType type);
static JsonbValue *wrapItemsInArray(const JsonValueList *items);
+static int compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
+ bool useTz, bool *have_error);
/****************** User interface to JsonPath executor ********************/
* SQL/JSON. Regarding jsonb_path_match(), this function doesn't have
* an analogy in SQL/JSON, so we define its behavior on our own.
*/
-Datum
-jsonb_path_exists(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_exists_internal(FunctionCallInfo fcinfo, bool tz)
{
Jsonb *jb = PG_GETARG_JSONB_P(0);
JsonPath *jp = PG_GETARG_JSONPATH_P(1);
silent = PG_GETARG_BOOL(3);
}
- res = executeJsonPath(jp, vars, jb, !silent, NULL);
+ res = executeJsonPath(jp, vars, jb, !silent, NULL, tz);
PG_FREE_IF_COPY(jb, 0);
PG_FREE_IF_COPY(jp, 1);
PG_RETURN_BOOL(res == jperOk);
}
+Datum
+jsonb_path_exists(PG_FUNCTION_ARGS)
+{
+ return jsonb_path_exists_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_exists_tz(PG_FUNCTION_ARGS)
+{
+ return jsonb_path_exists_internal(fcinfo, true);
+}
+
/*
* jsonb_path_exists_opr
* Implementation of operator "jsonb @? jsonpath" (2-argument version of
jsonb_path_exists_opr(PG_FUNCTION_ARGS)
{
/* just call the other one -- it can handle both cases */
- return jsonb_path_exists(fcinfo);
+ return jsonb_path_exists_internal(fcinfo, false);
}
/*
* Returns jsonpath predicate result item for the specified jsonb value.
* See jsonb_path_exists() comment for details regarding error handling.
*/
-Datum
-jsonb_path_match(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_match_internal(FunctionCallInfo fcinfo, bool tz)
{
Jsonb *jb = PG_GETARG_JSONB_P(0);
JsonPath *jp = PG_GETARG_JSONPATH_P(1);
silent = PG_GETARG_BOOL(3);
}
- (void) executeJsonPath(jp, vars, jb, !silent, &found);
+ (void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
PG_FREE_IF_COPY(jb, 0);
PG_FREE_IF_COPY(jp, 1);
if (!silent)
ereport(ERROR,
- (errcode(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED),
- errmsg(ERRMSG_SINGLETON_JSON_ITEM_REQUIRED),
- errdetail("expression should return a singleton boolean")));
+ (errcode(ERRCODE_SINGLETON_SQL_JSON_ITEM_REQUIRED),
+ errmsg("single boolean result is expected")));
PG_RETURN_NULL();
}
+Datum
+jsonb_path_match(PG_FUNCTION_ARGS)
+{
+ return jsonb_path_match_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_match_tz(PG_FUNCTION_ARGS)
+{
+ return jsonb_path_match_internal(fcinfo, true);
+}
+
/*
* jsonb_path_match_opr
* Implementation of operator "jsonb @@ jsonpath" (2-argument version of
jsonb_path_match_opr(PG_FUNCTION_ARGS)
{
/* just call the other one -- it can handle both cases */
- return jsonb_path_match(fcinfo);
+ return jsonb_path_match_internal(fcinfo, false);
}
/*
* Executes jsonpath for given jsonb document and returns result as
* rowset.
*/
-Datum
-jsonb_path_query(PG_FUNCTION_ARGS)
+static Datum
+jsonb_path_query_internal(FunctionCallInfo fcinfo, bool tz)
{
FuncCallContext *funcctx;
List *found;
vars = PG_GETARG_JSONB_P_COPY(2);
silent = PG_GETARG_BOOL(3);
- (void) executeJsonPath(jp, vars, jb, !silent, &found);
+ (void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
funcctx->user_fctx = JsonValueListGetList(&found);
SRF_RETURN_NEXT(funcctx, JsonbPGetDatum(JsonbValueToJsonb(v)));
}
+Datum
+jsonb_path_query(PG_FUNCTION_ARGS)
+{
+ return jsonb_path_query_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_tz(PG_FUNCTION_ARGS)
+{
+ return jsonb_path_query_internal(fcinfo, true);
+}
+
/*
* jsonb_path_query_array
* Executes jsonpath for given jsonb document and returns result as
* jsonb array.
*/
-Datum
-jsonb_path_query_array(FunctionCallInfo fcinfo)
+static Datum
+jsonb_path_query_array_internal(FunctionCallInfo fcinfo, bool tz)
{
Jsonb *jb = PG_GETARG_JSONB_P(0);
JsonPath *jp = PG_GETARG_JSONPATH_P(1);
Jsonb *vars = PG_GETARG_JSONB_P(2);
bool silent = PG_GETARG_BOOL(3);
- (void) executeJsonPath(jp, vars, jb, !silent, &found);
+ (void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
PG_RETURN_JSONB_P(JsonbValueToJsonb(wrapItemsInArray(&found)));
}
+Datum
+jsonb_path_query_array(PG_FUNCTION_ARGS)
+{
+ return jsonb_path_query_array_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_array_tz(PG_FUNCTION_ARGS)
+{
+ return jsonb_path_query_array_internal(fcinfo, true);
+}
+
/*
* jsonb_path_query_first
* Executes jsonpath for given jsonb document and returns first result
* item. If there are no items, NULL returned.
*/
-Datum
-jsonb_path_query_first(FunctionCallInfo fcinfo)
+static Datum
+jsonb_path_query_first_internal(FunctionCallInfo fcinfo, bool tz)
{
Jsonb *jb = PG_GETARG_JSONB_P(0);
JsonPath *jp = PG_GETARG_JSONPATH_P(1);
Jsonb *vars = PG_GETARG_JSONB_P(2);
bool silent = PG_GETARG_BOOL(3);
- (void) executeJsonPath(jp, vars, jb, !silent, &found);
+ (void) executeJsonPath(jp, vars, jb, !silent, &found, tz);
if (JsonValueListLength(&found) >= 1)
PG_RETURN_JSONB_P(JsonbValueToJsonb(JsonValueListHead(&found)));
PG_RETURN_NULL();
}
+Datum
+jsonb_path_query_first(PG_FUNCTION_ARGS)
+{
+ return jsonb_path_query_first_internal(fcinfo, false);
+}
+
+Datum
+jsonb_path_query_first_tz(PG_FUNCTION_ARGS)
+{
+ return jsonb_path_query_first_internal(fcinfo, true);
+}
+
/********************Execute functions for JsonPath**************************/
/*
* 'throwErrors' - whether we should throw suppressible errors
* 'result' - list to store result items into
*
- * Returns an error happens during processing or NULL on no error.
+ * Returns an error if a recoverable error happens during processing, or NULL
+ * on no error.
*
- * Note, jsonb and jsonpath values should be avaliable and untoasted during
+ * Note, jsonb and jsonpath values should be available and untoasted during
* work because JsonPathItem, JsonbValue and result item could have pointers
* into input values. If caller needs to just check if document matches
* jsonpath, then it doesn't provide a result arg. In this case executor
*/
static JsonPathExecResult
executeJsonPath(JsonPath *path, Jsonb *vars, Jsonb *json, bool throwErrors,
- JsonValueList *result)
+ JsonValueList *result, bool useTz)
{
JsonPathExecContext cxt;
JsonPathExecResult res;
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("jsonb containing jsonpath variables "
- "is not an object")));
+ errmsg("\"vars\" argument is not an object"),
+ errdetail("Jsonpath parameters should be encoded as key-value pairs of \"vars\" object.")));
}
cxt.vars = vars;
cxt.lastGeneratedObjectId = vars ? 2 : 1;
cxt.innermostArraySize = -1;
cxt.throwErrors = throwErrors;
+ cxt.useTz = useTz;
if (jspStrictAbsenseOfErrors(&cxt) && !result)
{
}
else if (!jspIgnoreStructuralErrors(cxt))
{
- StringInfoData keybuf;
- char *keystr;
-
Assert(found);
if (!jspThrowErrors(cxt))
return jperError;
- initStringInfo(&keybuf);
-
- keystr = pnstrdup(key.val.string.val, key.val.string.len);
- escape_json(&keybuf, keystr);
-
ereport(ERROR,
- (errcode(ERRCODE_JSON_MEMBER_NOT_FOUND), \
- errmsg(ERRMSG_JSON_MEMBER_NOT_FOUND),
- errdetail("JSON object does not contain key %s",
- keybuf.data)));
+ (errcode(ERRCODE_SQL_JSON_MEMBER_NOT_FOUND), \
+ errmsg("JSON object does not contain key \"%s\"",
+ pnstrdup(key.val.string.val,
+ key.val.string.len))));
}
}
else if (unwrap && JsonbType(jb) == jbvArray)
{
Assert(found);
RETURN_ERROR(ereport(ERROR,
- (errcode(ERRCODE_JSON_MEMBER_NOT_FOUND),
- errmsg(ERRMSG_JSON_MEMBER_NOT_FOUND),
- errdetail("jsonpath member accessor can "
- "only be applied to an object"))));
+ (errcode(ERRCODE_SQL_JSON_MEMBER_NOT_FOUND),
+ errmsg("jsonpath member accessor can only be applied to an object"))));
}
break;
res = executeNextItem(cxt, jsp, NULL, jb, found, true);
else if (!jspIgnoreStructuralErrors(cxt))
RETURN_ERROR(ereport(ERROR,
- (errcode(ERRCODE_JSON_ARRAY_NOT_FOUND),
- errmsg(ERRMSG_JSON_ARRAY_NOT_FOUND),
- errdetail("jsonpath wildcard array accessor "
- "can only be applied to an array"))));
+ (errcode(ERRCODE_SQL_JSON_ARRAY_NOT_FOUND),
+ errmsg("jsonpath wildcard array accessor can only be applied to an array"))));
break;
case jpiIndexArray:
index_from > index_to ||
index_to >= size))
RETURN_ERROR(ereport(ERROR,
- (errcode(ERRCODE_INVALID_JSON_SUBSCRIPT),
- errmsg(ERRMSG_INVALID_JSON_SUBSCRIPT),
- errdetail("jsonpath array subscript is "
- "out of bounds"))));
+ (errcode(ERRCODE_INVALID_SQL_JSON_SUBSCRIPT),
+ errmsg("jsonpath array subscript is out of bounds"))));
if (index_from < 0)
index_from = 0;
else if (!jspIgnoreStructuralErrors(cxt))
{
RETURN_ERROR(ereport(ERROR,
- (errcode(ERRCODE_JSON_ARRAY_NOT_FOUND),
- errmsg(ERRMSG_JSON_ARRAY_NOT_FOUND),
- errdetail("jsonpath array accessor can "
- "only be applied to an array"))));
+ (errcode(ERRCODE_SQL_JSON_ARRAY_NOT_FOUND),
+ errmsg("jsonpath array accessor can only be applied to an array"))));
}
break;
bool hasNext = jspGetNext(jsp, &elem);
if (cxt->innermostArraySize < 0)
- elog(ERROR, "evaluating jsonpath LAST outside of "
- "array subscript");
+ elog(ERROR, "evaluating jsonpath LAST outside of array subscript");
if (!hasNext && !found)
{
{
Assert(found);
RETURN_ERROR(ereport(ERROR,
- (errcode(ERRCODE_JSON_OBJECT_NOT_FOUND),
- errmsg(ERRMSG_JSON_OBJECT_NOT_FOUND),
- errdetail("jsonpath wildcard member accessor "
- "can only be applied to an object"))));
+ (errcode(ERRCODE_SQL_JSON_OBJECT_NOT_FOUND),
+ errmsg("jsonpath wildcard member accessor can only be applied to an object"))));
}
break;
{
if (!jspIgnoreStructuralErrors(cxt))
RETURN_ERROR(ereport(ERROR,
- (errcode(ERRCODE_JSON_ARRAY_NOT_FOUND),
- errmsg(ERRMSG_JSON_ARRAY_NOT_FOUND),
- errdetail("jsonpath item method .%s() "
- "can only be applied to an array",
- jspOperationName(jsp->type)))));
+ (errcode(ERRCODE_SQL_JSON_ARRAY_NOT_FOUND),
+ errmsg("jsonpath item method .%s() can only be applied to an array",
+ jspOperationName(jsp->type)))));
break;
}
if (have_error)
RETURN_ERROR(ereport(ERROR,
- (errcode(ERRCODE_NON_NUMERIC_JSON_ITEM),
- errmsg(ERRMSG_NON_NUMERIC_JSON_ITEM),
- errdetail("jsonpath item method .%s() "
- "can only be applied to "
- "a numeric value",
- jspOperationName(jsp->type)))));
+ (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
+ errmsg("jsonpath item method .%s() can only be applied to a numeric value",
+ jspOperationName(jsp->type)))));
res = jperOk;
}
else if (jb->type == jbvString)
if (have_error || isinf(val))
RETURN_ERROR(ereport(ERROR,
- (errcode(ERRCODE_NON_NUMERIC_JSON_ITEM),
- errmsg(ERRMSG_NON_NUMERIC_JSON_ITEM),
- errdetail("jsonpath item method .%s() can "
- "only be applied to a numeric value",
- jspOperationName(jsp->type)))));
+ (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
+ errmsg("jsonpath item method .%s() can only be applied to a numeric value",
+ jspOperationName(jsp->type)))));
jb = &jbv;
jb->type = jbvNumeric;
if (res == jperNotFound)
RETURN_ERROR(ereport(ERROR,
- (errcode(ERRCODE_NON_NUMERIC_JSON_ITEM),
- errmsg(ERRMSG_NON_NUMERIC_JSON_ITEM),
- errdetail("jsonpath item method .%s() "
- "can only be applied to a "
- "string or numeric value",
- jspOperationName(jsp->type)))));
+ (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)))));
res = executeNextItem(cxt, jsp, NULL, jb, found, true);
}
break;
+ case jpiDatetime:
+ if (unwrap && JsonbType(jb) == jbvArray)
+ return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
+
+ return executeDateTimeMethod(cxt, jsp, jb, found);
+
case jpiKeyValue:
if (unwrap && JsonbType(jb) == jbvArray)
return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false);
jspGetLeftArg(jsp, &larg);
jspGetRightArg(jsp, &rarg);
return executePredicate(cxt, jsp, &larg, &rarg, jb, true,
- executeComparison, NULL);
+ executeComparison, cxt);
case jpiStartsWith: /* 'whole STARTS WITH initial' */
jspGetLeftArg(jsp, &larg); /* 'whole' */
/*
* XXX: By standard only operands of multiplicative expressions are
- * unwrapped. We extend it to other binary arithmetics expressions too.
+ * unwrapped. We extend it to other binary arithmetic expressions too.
*/
jper = executeItemOptUnwrapResult(cxt, &elem, jb, true, &lseq);
if (jperIsError(jper))
if (JsonValueListLength(&lseq) != 1 ||
!(lval = getScalar(JsonValueListHead(&lseq), jbvNumeric)))
RETURN_ERROR(ereport(ERROR,
- (errcode(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED),
- errmsg(ERRMSG_SINGLETON_JSON_ITEM_REQUIRED),
- errdetail("left operand of binary jsonpath operator %s "
- "is not a singleton numeric value",
- jspOperationName(jsp->type)))));
+ (errcode(ERRCODE_SINGLETON_SQL_JSON_ITEM_REQUIRED),
+ errmsg("left operand of jsonpath operator %s is not a single numeric value",
+ jspOperationName(jsp->type)))));
if (JsonValueListLength(&rseq) != 1 ||
!(rval = getScalar(JsonValueListHead(&rseq), jbvNumeric)))
RETURN_ERROR(ereport(ERROR,
- (errcode(ERRCODE_SINGLETON_JSON_ITEM_REQUIRED),
- errmsg(ERRMSG_SINGLETON_JSON_ITEM_REQUIRED),
- errdetail("right operand of binary jsonpath operator %s "
- "is not a singleton numeric value",
- jspOperationName(jsp->type)))));
+ (errcode(ERRCODE_SINGLETON_SQL_JSON_ITEM_REQUIRED),
+ errmsg("right operand of jsonpath operator %s is not a single numeric value",
+ jspOperationName(jsp->type)))));
if (jspThrowErrors(cxt))
{
continue; /* skip non-numerics processing */
RETURN_ERROR(ereport(ERROR,
- (errcode(ERRCODE_JSON_NUMBER_NOT_FOUND),
- errmsg(ERRMSG_JSON_NUMBER_NOT_FOUND),
- errdetail("operand of unary jsonpath operator %s "
- "is not a numeric value",
- jspOperationName(jsp->type)))));
+ (errcode(ERRCODE_SQL_JSON_NUMBER_NOT_FOUND),
+ errmsg("operand of unary jsonpath operator %s is not a numeric value",
+ jspOperationName(jsp->type)))));
}
if (func)
/* Cache regex text and converted flags. */
if (!cxt->regex)
{
- uint32 flags = jsp->content.like_regex.flags;
-
cxt->regex =
cstring_to_text_with_len(jsp->content.like_regex.pattern,
jsp->content.like_regex.patternlen);
-
- /* Convert regex flags. */
- cxt->cflags = REG_ADVANCED;
-
- if (flags & JSP_REGEX_ICASE)
- cxt->cflags |= REG_ICASE;
- if (flags & JSP_REGEX_MLINE)
- cxt->cflags |= REG_NEWLINE;
- if (flags & JSP_REGEX_SLINE)
- cxt->cflags &= ~REG_NEWLINE;
- if (flags & JSP_REGEX_WSPACE)
- cxt->cflags |= REG_EXPANDED;
+ cxt->cflags = jspConvertRegexFlags(jsp->content.like_regex.flags);
}
if (RE_compile_and_execute(cxt->regex, str->val.string.val,
if (!(jb = getScalar(jb, jbvNumeric)))
RETURN_ERROR(ereport(ERROR,
- (errcode(ERRCODE_NON_NUMERIC_JSON_ITEM),
- errmsg(ERRMSG_NON_NUMERIC_JSON_ITEM),
- errdetail("jsonpath item method .%s() can only "
- "be applied to a numeric value",
- jspOperationName(jsp->type)))));
+ (errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
+ errmsg("jsonpath item method .%s() can only be applied to a numeric value",
+ jspOperationName(jsp->type)))));
- datum = NumericGetDatum(jb->val.numeric);
- datum = DirectFunctionCall1(func, datum);
+ datum = DirectFunctionCall1(func, NumericGetDatum(jb->val.numeric));
if (!jspGetNext(jsp, &next) && !found)
return jperOk;
return executeNextItem(cxt, jsp, &next, jb, found, false);
}
+/*
+ * Implementation of the .datetime() method.
+ *
+ * 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.
+ */
+static JsonPathExecResult
+executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
+ JsonbValue *jb, JsonValueList *found)
+{
+ JsonbValue jbvbuf;
+ Datum value;
+ text *datetime;
+ Oid typid;
+ int32 typmod = -1;
+ int tz = 0;
+ bool hasNext;
+ JsonPathExecResult res = jperNotFound;
+ JsonPathItem elem;
+
+ if (!(jb = getScalar(jb, jbvString)))
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+ errmsg("jsonpath item method .%s() can only be applied to a string",
+ jspOperationName(jsp->type)))));
+
+ datetime = cstring_to_text_with_len(jb->val.string.val,
+ jb->val.string.len);
+
+ if (jsp->content.arg)
+ {
+ text *template;
+ char *template_str;
+ int template_len;
+ bool have_error = false;
+
+ jspGetArg(jsp, &elem);
+
+ if (elem.type != jpiString)
+ elog(ERROR, "invalid jsonpath item type for .datetime() argument");
+
+ template_str = jspGetString(&elem, &template_len);
+
+ template = cstring_to_text_with_len(template_str,
+ template_len);
+
+ value = parse_datetime(datetime, template, true,
+ &typid, &typmod, &tz,
+ jspThrowErrors(cxt) ? NULL : &have_error);
+
+ if (have_error)
+ res = jperError;
+ else
+ res = jperOk;
+ }
+ else
+ {
+ /*
+ * According to SQL/JSON standard enumerate ISO formats for: date,
+ * timetz, time, timestamptz, timestamp.
+ */
+ static const char *fmt_str[] =
+ {
+ "yyyy-mm-dd",
+ "HH24:MI:SS TZH:TZM",
+ "HH24:MI:SS TZH",
+ "HH24:MI:SS",
+ "yyyy-mm-dd HH24:MI:SS TZH:TZM",
+ "yyyy-mm-dd HH24:MI:SS TZH",
+ "yyyy-mm-dd HH24:MI:SS"
+ };
+
+ /* cache for format texts */
+ static text *fmt_txt[lengthof(fmt_str)] = {0};
+ int i;
+
+ /* loop until datetime format fits */
+ for (i = 0; i < lengthof(fmt_str); i++)
+ {
+ bool have_error = false;
+
+ if (!fmt_txt[i])
+ {
+ MemoryContext oldcxt =
+ MemoryContextSwitchTo(TopMemoryContext);
+
+ fmt_txt[i] = cstring_to_text(fmt_str[i]);
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ value = parse_datetime(datetime, fmt_txt[i], true,
+ &typid, &typmod, &tz,
+ &have_error);
+
+ if (!have_error)
+ {
+ res = jperOk;
+ break;
+ }
+ }
+
+ if (res == jperNotFound)
+ RETURN_ERROR(ereport(ERROR,
+ (errcode(ERRCODE_INVALID_ARGUMENT_FOR_JSON_DATETIME_FUNCTION),
+ errmsg("datetime format is not unrecognized"),
+ errhint("use datetime template argument for explicit format specification"))));
+ }
+
+ pfree(datetime);
+
+ if (jperIsError(res))
+ return res;
+
+ hasNext = jspGetNext(jsp, &elem);
+
+ if (!hasNext && !found)
+ return res;
+
+ jb = hasNext ? &jbvbuf : palloc(sizeof(*jb));
+
+ jb->type = jbvDatetime;
+ jb->val.datetime.value = value;
+ jb->val.datetime.typid = typid;
+ jb->val.datetime.typmod = typmod;
+ jb->val.datetime.tz = tz;
+
+ return executeNextItem(cxt, jsp, &elem, jb, found, hasNext);
+}
+
/*
* Implementation of .keyvalue() method.
*
if (JsonbType(jb) != jbvObject || jb->type != jbvBinary)
RETURN_ERROR(ereport(ERROR,
- (errcode(ERRCODE_JSON_OBJECT_NOT_FOUND),
- errmsg(ERRMSG_JSON_OBJECT_NOT_FOUND),
- errdetail("jsonpath item method .%s() "
- "can only be applied to an object",
- jspOperationName(jsp->type)))));
+ (errcode(ERRCODE_SQL_JSON_OBJECT_NOT_FOUND),
+ errmsg("jsonpath item method .%s() can only be applied to an object",
+ jspOperationName(jsp->type)))));
jbc = jb->val.binary.data;
{
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
- errmsg("cannot find jsonpath variable '%s'",
+ errmsg("could not find jsonpath variable \"%s\"",
pnstrdup(varName, varNameLength))));
}
static JsonPathBool
executeComparison(JsonPathItem *cmp, JsonbValue *lv, JsonbValue *rv, void *p)
{
- return compareItems(cmp->type, lv, rv);
+ JsonPathExecContext *cxt = (JsonPathExecContext *) p;
+
+ return compareItems(cmp->type, lv, rv, cxt->useTz);
+}
+
+/*
+ * Perform per-byte comparison of two strings.
+ */
+static int
+binaryCompareStrings(const char *s1, int len1,
+ const char *s2, int len2)
+{
+ int cmp;
+
+ cmp = memcmp(s1, s2, Min(len1, len2));
+
+ if (cmp != 0)
+ return cmp;
+
+ if (len1 == len2)
+ return 0;
+
+ return len1 < len2 ? -1 : 1;
+}
+
+/*
+ * Compare two strings in the current server encoding using Unicode codepoint
+ * collation.
+ */
+static int
+compareStrings(const char *mbstr1, int mblen1,
+ const char *mbstr2, int mblen2)
+{
+ if (GetDatabaseEncoding() == PG_SQL_ASCII ||
+ GetDatabaseEncoding() == PG_UTF8)
+ {
+ /*
+ * It's known property of UTF-8 strings that their per-byte comparison
+ * result matches codepoints comparison result. ASCII can be
+ * considered as special case of UTF-8.
+ */
+ return binaryCompareStrings(mbstr1, mblen1, mbstr2, mblen2);
+ }
+ else
+ {
+ char *utf8str1,
+ *utf8str2;
+ int cmp,
+ utf8len1,
+ utf8len2;
+
+ /*
+ * We have to convert other encodings to UTF-8 first, then compare.
+ * Input strings may be not null-terminated and pg_server_to_any() may
+ * return them "as is". So, use strlen() only if there is real
+ * conversion.
+ */
+ utf8str1 = pg_server_to_any(mbstr1, mblen1, PG_UTF8);
+ utf8str2 = pg_server_to_any(mbstr2, mblen2, PG_UTF8);
+ utf8len1 = (mbstr1 == utf8str1) ? mblen1 : strlen(utf8str1);
+ utf8len2 = (mbstr2 == utf8str2) ? mblen2 : strlen(utf8str2);
+
+ cmp = binaryCompareStrings(utf8str1, utf8len1, utf8str2, utf8len2);
+
+ /*
+ * If pg_server_to_any() did no real conversion, then we actually
+ * compared original strings. So, we already done.
+ */
+ if (mbstr1 == utf8str1 && mbstr2 == utf8str2)
+ return cmp;
+
+ /* Free memory if needed */
+ if (mbstr1 != utf8str1)
+ pfree(utf8str1);
+ if (mbstr2 != utf8str2)
+ pfree(utf8str2);
+
+ /*
+ * When all Unicode codepoints are equal, return result of binary
+ * comparison. In some edge cases, same characters may have different
+ * representations in encoding. Then our behavior could diverge from
+ * standard. However, that allow us to do simple binary comparison
+ * for "==" operator, which is performance critical in typical cases.
+ * In future to implement strict standard conformance, we can do
+ * normalization of input JSON strings.
+ */
+ if (cmp == 0)
+ return binaryCompareStrings(mbstr1, mblen1, mbstr2, mblen2);
+ else
+ return cmp;
+ }
}
/*
* Compare two SQL/JSON items using comparison operation 'op'.
*/
static JsonPathBool
-compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2)
+compareItems(int32 op, JsonbValue *jb1, JsonbValue *jb2, bool useTz)
{
int cmp;
bool res;
jb2->val.string.val,
jb1->val.string.len) ? jpbFalse : jpbTrue;
- cmp = varstr_cmp(jb1->val.string.val, jb1->val.string.len,
- jb2->val.string.val, jb2->val.string.len,
- DEFAULT_COLLATION_OID);
+ cmp = compareStrings(jb1->val.string.val, jb1->val.string.len,
+ jb2->val.string.val, jb2->val.string.len);
+ break;
+ case jbvDatetime:
+ {
+ bool cast_error;
+
+ cmp = compareDatetime(jb1->val.datetime.value,
+ jb1->val.datetime.typid,
+ jb2->val.datetime.value,
+ jb2->val.datetime.typid,
+ useTz,
+ &cast_error);
+
+ if (cast_error)
+ return jpbUnknown;
+ }
break;
case jbvBinary:
compareNumeric(Numeric a, Numeric b)
{
return DatumGetInt32(DirectFunctionCall2(numeric_cmp,
- PointerGetDatum(a),
- PointerGetDatum(b)));
+ NumericGetDatum(a),
+ NumericGetDatum(b)));
}
static JsonbValue *
if (JsonValueListLength(&found) != 1 ||
!(jbv = getScalar(JsonValueListHead(&found), jbvNumeric)))
RETURN_ERROR(ereport(ERROR,
- (errcode(ERRCODE_INVALID_JSON_SUBSCRIPT),
- errmsg(ERRMSG_INVALID_JSON_SUBSCRIPT),
- errdetail("jsonpath array subscript is not a "
- "singleton numeric value"))));
+ (errcode(ERRCODE_INVALID_SQL_JSON_SUBSCRIPT),
+ errmsg("jsonpath array subscript is not a single numeric value"))));
numeric_index = DirectFunctionCall2(numeric_trunc,
NumericGetDatum(jbv->val.numeric),
if (have_error)
RETURN_ERROR(ereport(ERROR,
- (errcode(ERRCODE_INVALID_JSON_SUBSCRIPT),
- errmsg(ERRMSG_INVALID_JSON_SUBSCRIPT),
- errdetail("jsonpath array subscript is "
- "out of integer range"))));
+ (errcode(ERRCODE_INVALID_SQL_JSON_SUBSCRIPT),
+ errmsg("jsonpath array subscript is out of integer range"))));
return jperOk;
}
if (jvl->singleton)
{
it->value = jvl->singleton;
+ it->list = NIL;
it->next = NULL;
}
- else if (list_head(jvl->list) != NULL)
+ else if (jvl->list != NIL)
{
it->value = (JsonbValue *) linitial(jvl->list);
- it->next = lnext(list_head(jvl->list));
+ it->list = jvl->list;
+ it->next = list_second_cell(jvl->list);
}
else
{
it->value = NULL;
+ it->list = NIL;
it->next = NULL;
}
}
if (it->next)
{
it->value = lfirst(it->next);
- it->next = lnext(it->next);
+ it->next = lnext(it->list, it->next);
}
else
{
return pushJsonbValue(&ps, WJB_END_ARRAY, NULL);
}
+
+/* Check if the timezone required for casting from type1 to type2 is used */
+static void
+checkTimezoneIsUsedForCast(bool useTz, const char *type1, const char *type2)
+{
+ if (!useTz)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot convert value from %s to %s without timezone usage",
+ type1, type2),
+ errhint("Use *_tz() function for timezone support.")));
+}
+
+/* Convert time datum to timetz datum */
+static Datum
+castTimeToTimeTz(Datum time, bool useTz)
+{
+ checkTimezoneIsUsedForCast(useTz, "time", "timetz");
+
+ return DirectFunctionCall1(time_timetz, time);
+}
+
+/*---
+ * Compares 'ts1' and 'ts2' timestamp, assuming that ts1 might be overflowed
+ * during cast from another datatype.
+ *
+ * 'overflow1' specifies overflow of 'ts1' value:
+ * 0 - no overflow,
+ * -1 - exceed lower boundary,
+ * 1 - exceed upper boundary.
+ */
+static int
+cmpTimestampWithOverflow(Timestamp ts1, int overflow1, Timestamp ts2)
+{
+ /*
+ * All the timestamps we deal with in jsonpath are produced by
+ * to_datetime() method. So, they should be valid.
+ */
+ Assert(IS_VALID_TIMESTAMP(ts2));
+
+ /*
+ * Timestamp, which exceed lower (upper) bound, is always lower (higher)
+ * than any valid timestamp except minus (plus) infinity.
+ */
+ if (overflow1)
+ {
+ if (overflow1 < 0)
+ {
+ if (TIMESTAMP_IS_NOBEGIN(ts2))
+ return 1;
+ else
+ return -1;
+ }
+ if (overflow1 > 0)
+ {
+ if (TIMESTAMP_IS_NOEND(ts2))
+ return -1;
+ else
+ return 1;
+ }
+ }
+
+ return timestamp_cmp_internal(ts1, ts2);
+}
+
+/*
+ * Compare date to timestamptz without throwing overflow error during cast.
+ */
+static int
+cmpDateToTimestamp(DateADT date1, Timestamp ts2, bool useTz)
+{
+ TimestampTz ts1;
+ int overflow = 0;
+
+ ts1 = date2timestamp_opt_overflow(date1, &overflow);
+
+ return cmpTimestampWithOverflow(ts1, overflow, ts2);
+}
+
+/*
+ * Compare date to timestamptz without throwing overflow error during cast.
+ */
+static int
+cmpDateToTimestampTz(DateADT date1, TimestampTz tstz2, bool useTz)
+{
+ TimestampTz tstz1;
+ int overflow = 0;
+
+ checkTimezoneIsUsedForCast(useTz, "date", "timestamptz");
+
+ tstz1 = date2timestamptz_opt_overflow(date1, &overflow);
+
+ return cmpTimestampWithOverflow(tstz1, overflow, tstz2);
+}
+
+/*
+ * Compare timestamp to timestamptz without throwing overflow error during cast.
+ */
+static int
+cmpTimestampToTimestampTz(Timestamp ts1, TimestampTz tstz2, bool useTz)
+{
+ TimestampTz tstz1;
+ int overflow = 0;
+
+ checkTimezoneIsUsedForCast(useTz, "timestamp", "timestamptz");
+
+ tstz1 = timestamp2timestamptz_opt_overflow(ts1, &overflow);
+
+ return cmpTimestampWithOverflow(tstz1, overflow, tstz2);
+}
+
+/*
+ * Cross-type comparison of two datetime SQL/JSON items. If items are
+ * uncomparable *cast_error flag is set, otherwise *cast_error is unset.
+ * If the cast requires timezone and it is not used, then explicit error is thrown.
+ */
+static int
+compareDatetime(Datum val1, Oid typid1, Datum val2, Oid typid2,
+ bool useTz, bool *cast_error)
+{
+ PGFunction cmpfunc;
+
+ *cast_error = false;
+
+ switch (typid1)
+ {
+ case DATEOID:
+ switch (typid2)
+ {
+ case DATEOID:
+ cmpfunc = date_cmp;
+
+ break;
+
+ case TIMESTAMPOID:
+ return cmpDateToTimestamp(DatumGetDateADT(val1),
+ DatumGetTimestamp(val2),
+ useTz);
+
+ case TIMESTAMPTZOID:
+ return cmpDateToTimestampTz(DatumGetDateADT(val1),
+ DatumGetTimestampTz(val2),
+ useTz);
+
+ case TIMEOID:
+ case TIMETZOID:
+ *cast_error = true; /* uncomparable types */
+ return 0;
+
+ default:
+ elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u",
+ typid2);
+ }
+ break;
+
+ case TIMEOID:
+ switch (typid2)
+ {
+ case TIMEOID:
+ cmpfunc = time_cmp;
+
+ break;
+
+ case TIMETZOID:
+ val1 = castTimeToTimeTz(val1, useTz);
+ cmpfunc = timetz_cmp;
+
+ break;
+
+ case DATEOID:
+ case TIMESTAMPOID:
+ case TIMESTAMPTZOID:
+ *cast_error = true; /* uncomparable types */
+ return 0;
+
+ default:
+ elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u",
+ typid2);
+ }
+ break;
+
+ case TIMETZOID:
+ switch (typid2)
+ {
+ case TIMEOID:
+ val2 = castTimeToTimeTz(val2, useTz);
+ cmpfunc = timetz_cmp;
+
+ break;
+
+ case TIMETZOID:
+ cmpfunc = timetz_cmp;
+
+ break;
+
+ case DATEOID:
+ case TIMESTAMPOID:
+ case TIMESTAMPTZOID:
+ *cast_error = true; /* uncomparable types */
+ return 0;
+
+ default:
+ elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u",
+ typid2);
+ }
+ break;
+
+ case TIMESTAMPOID:
+ switch (typid2)
+ {
+ case DATEOID:
+ return -cmpDateToTimestamp(DatumGetDateADT(val2),
+ DatumGetTimestamp(val1),
+ useTz);
+
+ case TIMESTAMPOID:
+ cmpfunc = timestamp_cmp;
+
+ break;
+
+ case TIMESTAMPTZOID:
+ return cmpTimestampToTimestampTz(DatumGetTimestamp(val1),
+ DatumGetTimestampTz(val2),
+ useTz);
+
+ case TIMEOID:
+ case TIMETZOID:
+ *cast_error = true; /* uncomparable types */
+ return 0;
+
+ default:
+ elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u",
+ typid2);
+ }
+ break;
+
+ case TIMESTAMPTZOID:
+ switch (typid2)
+ {
+ case DATEOID:
+ return -cmpDateToTimestampTz(DatumGetDateADT(val2),
+ DatumGetTimestampTz(val1),
+ useTz);
+
+ case TIMESTAMPOID:
+ return -cmpTimestampToTimestampTz(DatumGetTimestamp(val2),
+ DatumGetTimestampTz(val1),
+ useTz);
+
+ case TIMESTAMPTZOID:
+ cmpfunc = timestamp_cmp;
+
+ break;
+
+ case TIMEOID:
+ case TIMETZOID:
+ *cast_error = true; /* uncomparable types */
+ return 0;
+
+ default:
+ elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u",
+ typid2);
+ }
+ break;
+
+ default:
+ elog(ERROR, "unrecognized SQL/JSON datetime type oid: %u", typid1);
+ }
+
+ if (*cast_error)
+ return 0; /* cast error */
+
+ return DatumGetInt32(DirectFunctionCall2(cmpfunc, val1, val2));
+}