if (PG_NARGS() == 4)
{
vars = PG_GETARG_JSONB_P(2);
silent = PG_GETARG_BOOL(3);
}
res = executeJsonPath(jp, vars, getJsonPathVariableFromJsonb,
countVariablesFromJsonb,
jb, !silent, NULL, tz);
PG_FREE_IF_COPY(jb, 0);
PG_FREE_IF_COPY(jp, 1);
if (jperIsError(res))
PG_RETURN_NULL();
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 *Implementationofoperator"jsonb@?jsonpath"(2-argumentversionof *jsonb_path_exists()).
*/
Datum
jsonb_path_exists_opr(PG_FUNCTION_ARGS)
{ /* just call the other one -- it can handle both cases */ return jsonb_path_exists_internal(fcinfo, false);
}
if (JsonValueListLength(&found) == 1)
{
JsonbValue *jbv = JsonValueListHead(&found);
if (jbv->type == jbvBool)
PG_RETURN_BOOL(jbv->val.boolean);
if (jbv->type == jbvNull)
PG_RETURN_NULL();
}
if (!silent)
ereport(ERROR,
(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 *Implementationofoperator"jsonb@@jsonpath"(2-argumentversionof *jsonb_path_match()).
*/
Datum
jsonb_path_match_opr(PG_FUNCTION_ARGS)
{ /* just call the other one -- it can handle both cases */ return jsonb_path_match_internal(fcinfo, false);
}
/* *jsonb_path_query *Executesjsonpathforgivenjsonbdocumentandreturnsresultas *rowset.
*/ static Datum
jsonb_path_query_internal(FunctionCallInfo fcinfo, bool tz)
{
FuncCallContext *funcctx;
List *found;
JsonbValue *v;
ListCell *c;
if (SRF_IS_FIRSTCALL())
{
JsonPath *jp;
Jsonb *jb;
MemoryContext oldcontext;
Jsonb *vars; bool silent;
JsonValueList found = {0};
switch (jsp->type)
{ case jpiNull: case jpiBool: case jpiNumeric: case jpiString: case jpiVariable:
{
JsonbValue vbuf;
JsonbValue *v; bool hasNext = jspGetNext(jsp, &elem);
if (!hasNext && !found && jsp->type != jpiVariable)
{ /* *Skipevaluation,butnotforvariables.Wemust *triggeranerrorforthemissingvariable.
*/
res = jperOk; break;
}
/* all boolean item types: */ case jpiAnd: case jpiOr: case jpiNot: case jpiIsUnknown: case jpiEqual: case jpiNotEqual: case jpiLess: case jpiGreater: case jpiLessOrEqual: case jpiGreaterOrEqual: case jpiExists: case jpiStartsWith: case jpiLikeRegex:
{
JsonPathBool st = executeBoolItem(cxt, jsp, jb, true);
res = appendBoolResult(cxt, jsp, found, st); break;
}
case jpiAdd: return executeBinaryArithmExpr(cxt, jsp, jb,
numeric_add_opt_error, found);
case jpiSub: return executeBinaryArithmExpr(cxt, jsp, jb,
numeric_sub_opt_error, found);
case jpiMul: return executeBinaryArithmExpr(cxt, jsp, jb,
numeric_mul_opt_error, found);
case jpiDiv: return executeBinaryArithmExpr(cxt, jsp, jb,
numeric_div_opt_error, found);
case jpiMod: return executeBinaryArithmExpr(cxt, jsp, jb,
numeric_mod_opt_error, found);
case jpiPlus: return executeUnaryArithmExpr(cxt, jsp, jb, NULL, found);
case jpiMinus: return executeUnaryArithmExpr(cxt, jsp, jb, numeric_uminus,
found);
case jpiAnyArray: if (JsonbType(jb) == jbvArray)
{ bool hasNext = jspGetNext(jsp, &elem);
res = executeItemUnwrapTargetArray(cxt, hasNext ? &elem : NULL,
jb, found, jspAutoUnwrap(cxt));
} elseif (jspAutoWrap(cxt))
res = executeNextItem(cxt, jsp, NULL, jb, found, true); elseif (!jspIgnoreStructuralErrors(cxt))
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_SQL_JSON_ARRAY_NOT_FOUND),
errmsg("jsonpath wildcard array accessor can only be applied to an array")))); break;
case jpiAnyKey: if (JsonbType(jb) == jbvObject)
{ bool hasNext = jspGetNext(jsp, &elem);
return executeAnyItem
(cxt, hasNext ? &elem : NULL,
jb->val.binary.data, found, 1, 1, 1, false, jspAutoUnwrap(cxt));
} elseif (unwrap && JsonbType(jb) == jbvArray) return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false); elseif (!jspIgnoreStructuralErrors(cxt))
{
Assert(found);
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_SQL_JSON_OBJECT_NOT_FOUND),
errmsg("jsonpath wildcard member accessor can only be applied to an object"))));
} break;
case jpiIndexArray: if (JsonbType(jb) == jbvArray || jspAutoWrap(cxt))
{ int innermostArraySize = cxt->innermostArraySize; int i; int size = JsonbArraySize(jb); bool singleton = size < 0; bool hasNext = jspGetNext(jsp, &elem);
if (singleton)
size = 1;
cxt->innermostArraySize = size; /* for LAST evaluation */
for (i = 0; i < jsp->content.array.nelems; i++)
{
JsonPathItem from;
JsonPathItem to;
int32 index;
int32 index_from;
int32 index_to; bool range = jspGetArraySubscript(jsp, &from,
&to, i);
res = getArrayIndex(cxt, &from, jb, &index_from);
if (jperIsError(res)) break;
if (range)
{
res = getArrayIndex(cxt, &to, jb, &index_to);
if (jperIsError(res)) break;
} else
index_to = index_from;
if (!jspIgnoreStructuralErrors(cxt) &&
(index_from < 0 ||
index_from > index_to ||
index_to >= size))
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_SQL_JSON_SUBSCRIPT),
errmsg("jsonpath array subscript is out of bounds"))));
if (index_from < 0)
index_from = 0;
if (index_to >= size)
index_to = size - 1;
res = jperNotFound;
for (index = index_from; index <= index_to; index++)
{
JsonbValue *v; bool copy;
if (singleton)
{
v = jb;
copy = true;
} else
{
v = getIthJsonbValueFromContainer(jb->val.binary.data,
(uint32) index);
if (v == NULL) continue;
copy = false;
}
if (!hasNext && !found) return jperOk;
res = executeNextItem(cxt, jsp, &elem, v, found,
copy);
if (jperIsError(res)) break;
if (res == jperOk && !found) break;
}
if (jperIsError(res)) break;
if (res == jperOk && !found) break;
}
cxt->innermostArraySize = innermostArraySize;
} elseif (!jspIgnoreStructuralErrors(cxt))
{
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_SQL_JSON_ARRAY_NOT_FOUND),
errmsg("jsonpath array accessor can only be applied to an array"))));
} break;
case jpiAny:
{ bool hasNext = jspGetNext(jsp, &elem);
/* first try without any intermediate steps */ if (jsp->content.anybounds.first == 0)
{ bool savedIgnoreStructuralErrors;
v = findJsonbValueFromContainer(jb->val.binary.data,
JB_FOBJECT, &key);
if (v != NULL)
{
res = executeNextItem(cxt, jsp, NULL,
v, found, false);
/* free value if it was not added to found list */ if (jspHasNext(jsp) || !found)
pfree(v);
} elseif (!jspIgnoreStructuralErrors(cxt))
{
Assert(found);
if (!jspThrowErrors(cxt)) return jperError;
ereport(ERROR,
(errcode(ERRCODE_SQL_JSON_MEMBER_NOT_FOUND), \
errmsg("JSON object does not contain key \"%s\"",
pnstrdup(key.val.string.val,
key.val.string.len))));
}
} elseif (unwrap && JsonbType(jb) == jbvArray) return executeItemUnwrapTargetArray(cxt, jsp, jb, found, false); elseif (!jspIgnoreStructuralErrors(cxt))
{
Assert(found);
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_SQL_JSON_MEMBER_NOT_FOUND),
errmsg("jsonpath member accessor can only be applied to an object"))));
} break;
case jpiCurrent:
res = executeNextItem(cxt, jsp, NULL, cxt->current,
found, true); break;
res = executeNextItem(cxt, jsp, NULL, jbv,
found, false);
} break;
case jpiSize:
{ int size = JsonbArraySize(jb);
if (size < 0)
{ if (!jspAutoWrap(cxt))
{ if (!jspIgnoreStructuralErrors(cxt))
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_SQL_JSON_ARRAY_NOT_FOUND),
errmsg("jsonpath item method .%s() can only be applied to an array",
jspOperationName(jsp->type))))); break;
}
val = float8in_internal(tmp,
NULL, "double precision",
tmp,
(Node *) &escontext);
if (escontext.error_occurred)
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("argument \"%s\" of jsonpath item method .%s() is invalid for type %s",
tmp, jspOperationName(jsp->type), "double precision")))); if (isinf(val) || isnan(val))
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("NaN or Infinity is not allowed for jsonpath item method .%s()",
jspOperationName(jsp->type)))));
res = jperOk;
} elseif (jb->type == jbvString)
{ /* cast string as double */ double val; char *tmp = pnstrdup(jb->val.string.val,
jb->val.string.len);
ErrorSaveContext escontext = {T_ErrorSaveContext};
val = float8in_internal(tmp,
NULL, "double precision",
tmp,
(Node *) &escontext);
if (escontext.error_occurred)
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("argument \"%s\" of jsonpath item method .%s() is invalid for type %s",
tmp, jspOperationName(jsp->type), "double precision")))); if (isinf(val) || isnan(val))
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("NaN or Infinity is not allowed for jsonpath item method .%s()",
jspOperationName(jsp->type)))));
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)))));
res = executeNextItem(cxt, jsp, NULL, jb, found, true);
} 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);
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("argument \"%s\" of jsonpath item method .%s() is invalid for type %s",
DatumGetCString(DirectFunctionCall1(numeric_out,
NumericGetDatum(jb->val.numeric))),
jspOperationName(jsp->type), "bigint"))));
datum = Int64GetDatum(val);
res = jperOk;
} elseif (jb->type == jbvString)
{ /* cast string as bigint */ char *tmp = pnstrdup(jb->val.string.val,
jb->val.string.len);
ErrorSaveContext escontext = {T_ErrorSaveContext}; bool noerr;
if (!noerr || escontext.error_occurred)
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("argument \"%s\" of jsonpath item method .%s() is invalid for type %s",
tmp, jspOperationName(jsp->type), "bigint"))));
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 (!noerr || escontext.error_occurred)
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("argument \"%s\" of jsonpath item method .%s() is invalid for type %s",
tmp, jspOperationName(jsp->type), "boolean"))));
res = jperOk;
} elseif (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("argument \"%s\" of jsonpath item method .%s() is invalid for type %s",
tmp, jspOperationName(jsp->type), "boolean"))));
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 boolean, string, or numeric value",
jspOperationName(jsp->type)))));
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("NaN or Infinity is not allowed for jsonpath item method .%s()",
jspOperationName(jsp->type)))));
if (jsp->type == jpiDecimal)
numstr = DatumGetCString(DirectFunctionCall1(numeric_out,
NumericGetDatum(num)));
res = jperOk;
} elseif (jb->type == jbvString)
{ /* cast string as number */
Datum datum; bool noerr;
ErrorSaveContext escontext = {T_ErrorSaveContext};
if (!noerr || escontext.error_occurred)
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("argument \"%s\" of jsonpath item method .%s() is invalid for type %s",
numstr, jspOperationName(jsp->type), "numeric"))));
num = DatumGetNumeric(datum); if (numeric_is_nan(num) || numeric_is_inf(num))
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("NaN or Infinity is not allowed for jsonpath item method .%s()",
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)))));
/* *Ifwehavearguments,thentheymustbetheprecisionand *optionalscaleusedin.decimal().Convertthemtothe *typmodequivalentandthentruncatethenumericvalueper *thistypmoddetails.
*/ 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)))));
}
if (!noerr || escontext.error_occurred)
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("argument \"%s\" of jsonpath item method .%s() is invalid for type %s",
numstr, jspOperationName(jsp->type), "numeric"))));
num = DatumGetNumeric(numdatum);
pfree(arrtypmod);
}
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("argument \"%s\" of jsonpath item method .%s() is invalid for type %s",
DatumGetCString(DirectFunctionCall1(numeric_out,
NumericGetDatum(jb->val.numeric))),
jspOperationName(jsp->type), "integer"))));
datum = Int32GetDatum(val);
res = jperOk;
} elseif (jb->type == jbvString)
{ /* cast string as integer */ char *tmp = pnstrdup(jb->val.string.val,
jb->val.string.len);
ErrorSaveContext escontext = {T_ErrorSaveContext}; bool noerr;
if (!noerr || escontext.error_occurred)
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("argument \"%s\" of jsonpath item method .%s() is invalid for type %s",
tmp, jspOperationName(jsp->type), "integer"))));
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)))));
JsonEncodeDateTime(buf,
jb->val.datetime.value,
jb->val.datetime.typid,
&jb->val.datetime.tz);
tmp = pstrdup(buf);
} 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 boolean, string, numeric, or datetime value",
jspOperationName(jsp->type))))); break;
}
jb = &jbv;
Assert(tmp != NULL); /* We must have set tmp above */
jb->val.string.val = tmp;
jb->val.string.len = strlen(jb->val.string.val);
jb->type = jbvString;
res = executeNextItem(cxt, jsp, NULL, jb, found, true);
} break;
case jpiIsUnknown:
jspGetArg(jsp, &larg);
res = executeBoolItem(cxt, &larg, jb, false); return res == jpbUnknown ? jpbTrue : jpbFalse;
case jpiEqual: case jpiNotEqual: case jpiLess: case jpiGreater: case jpiLessOrEqual: case jpiGreaterOrEqual:
jspGetLeftArg(jsp, &larg);
jspGetRightArg(jsp, &rarg); return executePredicate(cxt, jsp, &larg, &rarg, jb, true,
executeComparison, cxt);
/* Left argument is always auto-unwrapped. */
res = executeItemOptUnwrapResultNoThrow(cxt, larg, jb, true, &lseq); if (jperIsError(res)) return jpbUnknown;
if (rarg)
{ /* Right argument is conditionally auto-unwrapped. */
res = executeItemOptUnwrapResultNoThrow(cxt, rarg, jb,
unwrapRightArg, &rseq); if (jperIsError(res)) return jpbUnknown;
}
JsonValueListInitIterator(&lseq, &lseqit); while ((lval = JsonValueListNext(&lseq, &lseqit)))
{
JsonValueListIterator rseqit;
JsonbValue *rval; bool first = true;
/* Loop over right arg sequence or do single pass otherwise */ while (rarg ? (rval != NULL) : first)
{
JsonPathBool res = exec(pred, lval, rval, param);
if (res == jpbUnknown)
{ if (jspStrictAbsenceOfErrors(cxt)) return jpbUnknown;
if (JsonValueListLength(&lseq) != 1 ||
!(lval = getScalar(JsonValueListHead(&lseq), jbvNumeric)))
RETURN_ERROR(ereport(ERROR,
(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_SQL_JSON_ITEM_REQUIRED),
errmsg("right operand of jsonpath operator %s is not a single numeric value",
jspOperationName(jsp->type)))));
if (jspThrowErrors(cxt))
{
res = func(lval->val.numeric, rval->val.numeric, NULL);
} else
{ bool error = false;
res = func(lval->val.numeric, rval->val.numeric, &error);
if (error) return jperError;
}
if (!jspGetNext(jsp, &elem) && !found) return jperOk;
JsonValueListInitIterator(&seq, &it); while ((val = JsonValueListNext(&seq, &it)))
{ if ((val = getScalar(val, jbvNumeric)))
{ if (!found && !hasNext) return jperOk;
} else
{ if (!found && !hasNext) continue; /* skip non-numerics processing */
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_SQL_JSON_NUMBER_NOT_FOUND),
errmsg("operand of unary jsonpath operator %s is not a numeric value",
jspOperationName(jsp->type)))));
}
if (func)
val->val.numeric =
DatumGetNumeric(DirectFunctionCall1(func,
NumericGetDatum(val->val.numeric)));
if (!(jb = getScalar(jb, jbvNumeric)))
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_NON_NUMERIC_SQL_JSON_ITEM),
errmsg("jsonpath item method .%s() can only be applied to a numeric value",
jspOperationName(jsp->type)))));
datum = DirectFunctionCall1(func, NumericGetDatum(jb->val.numeric));
if (!jspGetNext(jsp, &next) && !found) return jperOk;
/* *Implementationofthe.datetime()andrelatedmethods. * *Convertsastringintoadate/timevalue.Theactualtypeisdeterminedat *runtime. *Ifanargumentisprovided,thisargumentisusedasatemplatestring. *Otherwise,thefirstfittingISOformatisselected. * *.date(),.time(),.time_tz(),.timestamp(),.timestamp_tz()methodsdon't *haveaformat,soISOformatisused.However,exceptfor.date(),theyall *takeanoptionaltimeprecision.
*/ static JsonPathExecResult
executeDateTimeMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
JsonbValue *jb, JsonValueList *found)
{
JsonbValue jbvbuf;
Datum value;
text *datetime;
Oid collid;
Oid typid;
int32 typmod = -1; int tz = 0; bool hasNext;
JsonPathExecResult res = jperNotFound;
JsonPathItem elem;
int32 time_precision = -1;
if (!(jb = getScalar(jb, jbvString)))
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION),
errmsg("jsonpath item method .%s() can only be applied to a string",
jspOperationName(jsp->type)))));
if (escontext.error_occurred)
res = jperError; else
res = jperOk;
} else
{ /* *AccordingtoSQL/JSONstandardenumerateISOformatsfor:date, *timetz,time,timestamptz,timestamp. * *WealsosupportISO8601format(with"T")fortimestamps,because *to_json[b]()functionsusethisformat.
*/ staticconstchar *fmt_str[] =
{ "yyyy-mm-dd", /* date */ "HH24:MI:SS.USTZ", /* timetz */ "HH24:MI:SSTZ", "HH24:MI:SS.US", /* time without tz */ "HH24:MI:SS", "yyyy-mm-dd HH24:MI:SS.USTZ", /* timestamptz */ "yyyy-mm-dd HH24:MI:SSTZ", "yyyy-mm-dd\"T\"HH24:MI:SS.USTZ", "yyyy-mm-dd\"T\"HH24:MI:SSTZ", "yyyy-mm-dd HH24:MI:SS.US", /* timestamp without tz */ "yyyy-mm-dd HH24:MI:SS", "yyyy-mm-dd\"T\"HH24:MI:SS.US", "yyyy-mm-dd\"T\"HH24:MI:SS"
};
/* cache for format texts */ 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++)
{
ErrorSaveContext escontext = {T_ErrorSaveContext};
if (!fmt_txt[i])
{
MemoryContext oldcxt =
MemoryContextSwitchTo(TopMemoryContext);
if (!escontext.error_occurred)
{
res = jperOk;
break;
}
}
if (res == jperNotFound)
{
if (jsp->type == jpiDatetime)
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION),
errmsg("%s format is not recognized: \"%s\"", "datetime", 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("%s format is not recognized: \"%s\"", "date", text_to_cstring(datetime)))));
break;
case TIMESTAMPOID:
value = DirectFunctionCall1(timestamp_date,
value);
break;
case TIMESTAMPTZOID:
checkTimezoneIsUsedForCast(cxt->useTz, "timestamptz", "date");
value = DirectFunctionCall1(timestamptz_date,
value);
break;
default:
elog(ERROR, "type with oid %u 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("%s format is not recognized: \"%s\"", "time", text_to_cstring(datetime)))));
break;
case TIMEOID: /* Nothing to do for TIME */
break;
case TIMETZOID:
checkTimezoneIsUsedForCast(cxt->useTz, "timetz", "time");
value = DirectFunctionCall1(timetz_time,
value);
break;
case TIMESTAMPOID:
value = DirectFunctionCall1(timestamp_time,
value);
break;
case TIMESTAMPTZOID:
checkTimezoneIsUsedForCast(cxt->useTz, "timestamptz", "time");
value = DirectFunctionCall1(timestamptz_time,
value);
break;
default:
elog(ERROR, "type with oid %u 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("%s format is not recognized: \"%s\"", "time_tz", text_to_cstring(datetime)))));
break;
case TIMEOID:
checkTimezoneIsUsedForCast(cxt->useTz, "time", "timetz");
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 %u 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("%s format is not recognized: \"%s\"", "timestamp", text_to_cstring(datetime)))));
break;
case TIMESTAMPOID: /* Nothing to do for TIMESTAMP */
break;
case TIMESTAMPTZOID:
checkTimezoneIsUsedForCast(cxt->useTz, "timestamptz", "timestamp");
value = DirectFunctionCall1(timestamptz_timestamp,
value);
break;
default:
elog(ERROR, "type with oid %u 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) /* should not happen */
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION),
errmsg("time precision of jsonpath item method .%s() is invalid",
jspOperationName(jsp->type)))));
value = TimestampGetDatum(result);
/* Update the typmod value with the user-given precision */
typmod = time_precision;
}
/* Convert result type to timestamp with time zone */
switch (typid)
{
case DATEOID:
checkTimezoneIsUsedForCast(cxt->useTz, "date", "timestamptz");
/*
* Get the timezone value explicitly since JsonbValue
* keeps that separate.
*/
j2date(DatumGetDateADT(value) + POSTGRES_EPOCH_JDATE,
&(tm.tm_year), &(tm.tm_mon), &(tm.tm_mday));
tm.tm_hour = 0;
tm.tm_min = 0;
tm.tm_sec = 0;
tz = DetermineTimeZoneOffset(&tm, session_timezone);
value = DirectFunctionCall1(date_timestamptz,
value);
break;
case TIMEOID:
case TIMETZOID:
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION),
errmsg("%s format is not recognized: \"%s\"", "timestamp_tz", text_to_cstring(datetime)))));
break;
case TIMESTAMPOID:
checkTimezoneIsUsedForCast(cxt->useTz, "timestamp", "timestamptz");
/*
* Get the timezone value explicitly since JsonbValue
* keeps that separate.
*/
if (timestamp2tm(DatumGetTimestamp(value), NULL, &tm,
&fsec, NULL, NULL) == 0)
tz = DetermineTimeZoneOffset(&tm,
session_timezone);
value = DirectFunctionCall1(timestamp_timestamptz,
value);
break;
case TIMESTAMPTZOID: /* Nothing to do for TIMESTAMPTZ */
break;
default:
elog(ERROR, "type with oid %u 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) /* should not happen */
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION),
errmsg("time precision of jsonpath item method .%s() is invalid",
jspOperationName(jsp->type)))));
value = TimestampTzGetDatum(result);
/* Update the typmod value with the user-given precision */
typmod = time_precision;
}
/*
* Implementation of .keyvalue() method.
*
* .keyvalue() method returns a sequence of object's key-value pairs in the
* following format: '{ "key": key, "value": value, "id": id }'.
*
* "id" field is an object identifier which is constructed from the two parts:
* base object id and its binary offset in base object's jsonb:
* id = 10000000000 * base_object_id + obj_offset_in_base_object
*
* 10000000000 (10^10) -- is a first round decimal number greater than 2^32
* (maximal offset in jsonb). Decimal multiplier is used here to improve the
* readability of identifiers.
*
* Base object is usually a root object of the path: context item '$' or path
* variable '$var', literals can't produce objects for now. But if the path
* contains generated objects (.keyvalue() itself, for example), then they
* become base object for the subsequent .keyvalue().
*
* Id of '$' is 0. Id of '$var' is its ordinal (positive) number in the list
* of variables (see getJsonPathVariable()). Ids for generated objects
* are assigned using global counter JsonPathExecContext.lastGeneratedObjectId.
*/
static JsonPathExecResult
executeKeyValueMethod(JsonPathExecContext *cxt, JsonPathItem *jsp,
JsonbValue *jb, JsonValueList *found)
{
JsonPathExecResult res = jperNotFound;
JsonPathItem next;
JsonbContainer *jbc;
JsonbValue key;
JsonbValue val;
JsonbValue idval;
JsonbValue keystr;
JsonbValue valstr;
JsonbValue idstr;
JsonbIterator *it;
JsonbIteratorToken tok;
int64 id;
bool hasNext;
if (JsonbType(jb) != jbvObject || jb->type != jbvBinary)
RETURN_ERROR(ereport(ERROR,
(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;
if (!JsonContainerSize(jbc))
return jperNotFound; /* no key-value pairs */
/* construct object id from its base object and offset inside that */ id = jb->type != jbvBinary ? 0 :
(int64) ((char *) jbc - (char *) cxt->baseObject.jbc); id += (int64) cxt->baseObject.id * INT64CONST(10000000000);
/*
* Convert jsonpath's scalar or variable node to actual jsonb value.
*
* If node is a variable then its id returned, otherwise 0 returned.
*/
static void
getJsonPathItem(JsonPathExecContext *cxt, JsonPathItem *item,
JsonbValue *value)
{
switch (item->type)
{
case jpiNull:
value->type = jbvNull;
break;
case jpiBool:
value->type = jbvBool;
value->val.boolean = jspGetBool(item);
break;
case jpiNumeric:
value->type = jbvNumeric;
value->val.numeric = jspGetNumeric(item);
break;
case jpiString:
value->type = jbvString;
value->val.string.val = jspGetString(item,
&value->val.string.len);
break;
case jpiVariable:
getJsonPathVariable(cxt, item, value);
return;
default:
elog(ERROR, "unexpected jsonpath item type");
}
}
/*
* Returns the computed value of a JSON path variable with given name.
*/
static JsonbValue *
GetJsonPathVar(void *cxt, char *varName, int varNameLen,
JsonbValue *baseObject, int *baseObjectId)
{
JsonPathVariable *var = NULL;
List *vars = cxt;
ListCell *lc;
JsonbValue *result;
int id = 1;
JsonItemFromDatum(JsonbPGetDatum(jb), JSONBOID, -1, res);
break;
}
default:
ereport(ERROR,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("could not convert value of type %s to jsonpath",
format_type_be(typid)));
}
}
/* Initialize numeric value from the given datum */
static void
JsonbValueInitNumericDatum(JsonbValue *jbv, Datum num)
{
jbv->type = jbvNumeric;
jbv->val.numeric = DatumGetNumeric(num);
}
/*
* Get the value of variable passed to jsonpath executor
*/
static void
getJsonPathVariable(JsonPathExecContext *cxt, JsonPathItem *variable,
JsonbValue *value)
{
char *varName;
int varNameLength;
JsonbValue baseObject;
int baseObjectId;
JsonbValue *v;
/*
* Definition of JsonPathGetVarCallback for when JsonPathExecContext.vars
* is specified as a jsonb value.
*/
static JsonbValue *
getJsonPathVariableFromJsonb(void *varsJsonb, char *varName, int varNameLength,
JsonbValue *baseObject, int *baseObjectId)
{
Jsonb *vars = varsJsonb;
JsonbValue tmp;
JsonbValue *result;
/*
* Definition of JsonPathCountVarsCallback for when JsonPathExecContext.vars
* is specified as a jsonb value.
*/
static int
countVariablesFromJsonb(void *varsJsonb)
{
Jsonb *vars = varsJsonb;
if (vars && !JsonContainerIsObject(&vars->root))
{
ereport(ERROR,
errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("\"vars\" argument is not an object"),
errdetail("Jsonpath parameters should be encoded as key-value pairs of \"vars\" object."));
}
/* count of base objects */
return vars != NULL ? 1 : 0;
}
/**************** Support functions for JsonPath execution *****************/
/*
* Returns the size of an array item, or -1 if item is not an array.
*/
static int
JsonbArraySize(JsonbValue *jb)
{
Assert(jb->type != jbvArray);
if (jb->type == jbvBinary)
{
JsonbContainer *jbc = jb->val.binary.data;
if (JsonContainerIsArray(jbc) && !JsonContainerIsScalar(jbc))
return JsonContainerSize(jbc);
}
/*
* 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);
/*
* 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, bool useTz)
{
int cmp;
bool res;
if (jb1->type != jb2->type)
{
if (jb1->type == jbvNull || jb2->type == jbvNull)
/*
* Equality and order comparison of nulls to non-nulls returns
* always false, but inequality comparison returns true.
*/
return op == jpiNotEqual ? jpbTrue : jpbFalse;
/* Non-null items of different types are not comparable. */
return jpbUnknown;
}
switch (jb1->type)
{
case jbvNull:
cmp = 0;
break;
case jbvBool:
cmp = jb1->val.boolean == jb2->val.boolean ? 0 :
jb1->val.boolean ? 1 : -1;
break;
case jbvNumeric:
cmp = compareNumeric(jb1->val.numeric, jb2->val.numeric);
break;
case jbvString:
if (op == jpiEqual)
return jb1->val.string.len != jb2->val.string.len ||
memcmp(jb1->val.string.val,
jb2->val.string.val,
jb1->val.string.len) ? jpbFalse : jpbTrue;
case jbvBinary:
case jbvArray:
case jbvObject:
return jpbUnknown; /* non-scalars are not comparable */
default:
elog(ERROR, "invalid jsonb value type %d", jb1->type);
}
switch (op)
{
case jpiEqual:
res = (cmp == 0);
break;
case jpiNotEqual:
res = (cmp != 0);
break;
case jpiLess:
res = (cmp < 0);
break;
case jpiGreater:
res = (cmp > 0);
break;
case jpiLessOrEqual:
res = (cmp <= 0);
break;
case jpiGreaterOrEqual:
res = (cmp >= 0);
break;
default:
elog(ERROR, "unrecognized jsonpath operation: %d", op);
return jpbUnknown;
}
return res ? jpbTrue : jpbFalse;
}
/* Compare two numerics */
static int
compareNumeric(Numeric a, Numeric b)
{
return DatumGetInt32(DirectFunctionCall2(numeric_cmp,
NumericGetDatum(a),
NumericGetDatum(b)));
}
/*
* Execute array subscript expression and convert resulting numeric item to
* the integer type with truncation.
*/
static JsonPathExecResult
getArrayIndex(JsonPathExecContext *cxt, JsonPathItem *jsp, JsonbValue *jb,
int32 *index)
{
JsonbValue *jbv;
JsonValueList found = {0};
JsonPathExecResult res = executeItem(cxt, jsp, jb, &found);
Datum numeric_index;
bool have_error = false;
if (jperIsError(res))
return res;
if (JsonValueListLength(&found) != 1 ||
!(jbv = getScalar(JsonValueListHead(&found), jbvNumeric)))
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_SQL_JSON_SUBSCRIPT),
errmsg("jsonpath array subscript is not a single numeric value"))));
if (have_error)
RETURN_ERROR(ereport(ERROR,
(errcode(ERRCODE_INVALID_SQL_JSON_SUBSCRIPT),
errmsg("jsonpath array subscript is out of integer range"))));
return jperOk;
}
/* Save base object and its id needed for the execution of .keyvalue(). */
static JsonBaseObjectInfo
setBaseObject(JsonPathExecContext *cxt, JsonbValue *jbv, int32 id)
{
JsonBaseObjectInfo baseObject = cxt->baseObject;
/*
* Get the next item from the sequence advancing iterator.
*/
static JsonbValue *
JsonValueListNext(const JsonValueList *jvl, JsonValueListIterator *it)
{
JsonbValue *result = it->value;
/*
* Initialize a binary JsonbValue with the given jsonb container.
*/
static JsonbValue *
JsonbInitBinary(JsonbValue *jbv, Jsonb *jb)
{
jbv->type = jbvBinary;
jbv->val.binary.data = &jb->root;
jbv->val.binary.len = VARSIZE_ANY_EXHDR(jb);
return jbv;
}
/*
* Returns jbv* type of JsonbValue. Note, it never returns jbvBinary as is.
*/
static int
JsonbType(JsonbValue *jb)
{
int type = jb->type;
if (jb->type == jbvBinary)
{
JsonbContainer *jbc = jb->val.binary.data;
/* Scalars should be always extracted during jsonpath execution. */
Assert(!JsonContainerIsScalar(jbc));
if (JsonContainerIsObject(jbc)) type = jbvObject;
else if (JsonContainerIsArray(jbc)) type = jbvArray;
else
elog(ERROR, "invalid jsonb container type: 0x%08x", jbc->header);
}
return type;
}
/* Get scalar of given type or NULL on type mismatch */
static JsonbValue *
getScalar(JsonbValue *scalar, enum jbvType type)
{
/* Scalars should be always extracted during jsonpath execution. */
Assert(scalar->type != jbvBinary ||
!JsonContainerIsScalar(scalar->val.binary.data));
return scalar->type == type ? scalar : NULL;
}
/* Construct a JSON array from the item list */
static JsonbValue *
wrapItemsInArray(const JsonValueList *items)
{
JsonbParseState *ps = NULL;
JsonValueListIterator it;
JsonbValue *jbv;
pushJsonbValue(&ps, WJB_BEGIN_ARRAY, NULL);
JsonValueListInitIterator(items, &it);
while ((jbv = JsonValueListNext(items, &it)))
pushJsonbValue(&ps, WJB_ELEM, jbv);
/* 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 time zone usage",
type1, type2),
errhint("Use *_tz() function for time zone support.")));
}
/* Convert time datum to timetz datum */
static Datum
castTimeToTimeTz(Datum time, bool useTz)
{
checkTimezoneIsUsedForCast(useTz, "time", "timetz");
return DirectFunctionCall1(time_timetz, time);
}
/*
* Compare date to timestamp.
* Note that this doesn't involve any timezone considerations.
*/
static int
cmpDateToTimestamp(DateADT date1, Timestamp ts2, bool useTz)
{
return date_cmp_timestamp_internal(date1, ts2);
}
/*
* Compare date to timestamptz.
*/
static int
cmpDateToTimestampTz(DateADT date1, TimestampTz tstz2, bool useTz)
{
checkTimezoneIsUsedForCast(useTz, "date", "timestamptz");
/*
* 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;
/*
* Executor-callable JSON_EXISTS implementation
*
* Returns NULL instead of throwing errors if 'error' is not NULL, setting
* *error to true.
*/
bool
JsonPathExists(Datum jb, JsonPath *jp, bool *error, List *vars)
{
JsonPathExecResult res;
res = executeJsonPath(jp, vars,
GetJsonPathVar, CountJsonPathVars,
DatumGetJsonbP(jb), !error, NULL, true);
Assert(error || !jperIsError(res));
if (error && jperIsError(res))
*error = true;
return res == jperOk;
}
/*
* Executor-callable JSON_QUERY implementation
*
* Returns NULL instead of throwing errors if 'error' is not NULL, setting
* *error to true. *empty is set to true if no match is found.
*/
Datum
JsonPathQuery(Datum jb, JsonPath *jp, JsonWrapper wrapper, bool *empty,
bool *error, List *vars,
const char *column_name)
{
JsonbValue *singleton;
bool wrap;
JsonValueList found = {0};
JsonPathExecResult res;
int count;
/*
* Determine whether to wrap the result in a JSON array or not.
*
* First, count the number of SQL/JSON items in the returned
* JsonValueList. If the list is empty (singleton == NULL), no wrapping is
* necessary.
*
* If the wrapper mode is JSW_NONE or JSW_UNSPEC, wrapping is explicitly
* disabled. This enforces a WITHOUT WRAPPER clause, which is also the
* default when no WRAPPER clause is specified.
*
* If the mode is JSW_UNCONDITIONAL, wrapping is enforced regardless of
* the number of SQL/JSON items, enforcing a WITH WRAPPER or WITH
* UNCONDITIONAL WRAPPER clause.
*
* For JSW_CONDITIONAL, wrapping occurs only if there is more than one
* SQL/JSON item in the list, enforcing a WITH CONDITIONAL WRAPPER clause.
*/
count = JsonValueListLength(&found);
singleton = count > 0 ? JsonValueListHead(&found) : NULL;
if (singleton == NULL)
wrap = false;
else if (wrapper == JSW_NONE || wrapper == JSW_UNSPEC)
wrap = false;
else if (wrapper == JSW_UNCONDITIONAL)
wrap = true;
else if (wrapper == JSW_CONDITIONAL)
wrap = count > 1;
else
{
elog(ERROR, "unrecognized json wrapper %d", (int) wrapper);
wrap = false;
}
if (wrap)
return JsonbPGetDatum(JsonbValueToJsonb(wrapItemsInArray(&found)));
/* No wrapping means only one item is expected. */
if (count > 1)
{
if (error)
{
*error = true;
return (Datum) 0;
}
if (column_name)
ereport(ERROR,
(errcode(ERRCODE_MORE_THAN_ONE_SQL_JSON_ITEM),
errmsg("JSON path expression for column \"%s\" must return single item when no wrapper is requested",
column_name),
errhint("Use the WITH WRAPPER clause to wrap SQL/JSON items into an array.")));
else
ereport(ERROR,
(errcode(ERRCODE_MORE_THAN_ONE_SQL_JSON_ITEM),
errmsg("JSON path expression in JSON_QUERY must return single item when no wrapper is requested"),
errhint("Use the WITH WRAPPER clause to wrap SQL/JSON items into an array.")));
}
if (singleton)
return JsonbPGetDatum(JsonbValueToJsonb(singleton));
*empty = true;
return PointerGetDatum(NULL);
}
/*
* Executor-callable JSON_VALUE implementation
*
* Returns NULL instead of throwing errors if 'error' is not NULL, setting
* *error to true. *empty is set to true if no match is found.
*/
JsonbValue *
JsonPathValue(Datum jb, JsonPath *jp, bool *empty, bool *error, List *vars,
const char *column_name)
{
JsonbValue *res;
JsonValueList found = {0};
JsonPathExecResult jper PG_USED_FOR_ASSERTS_ONLY;
int count;
/* JSON_VALUE expects to get only singletons. */
if (count > 1)
{
if (error)
{
*error = true;
return NULL;
}
if (column_name)
ereport(ERROR,
(errcode(ERRCODE_MORE_THAN_ONE_SQL_JSON_ITEM),
errmsg("JSON path expression for column \"%s\" must return single scalar item",
column_name)));
else
ereport(ERROR,
(errcode(ERRCODE_MORE_THAN_ONE_SQL_JSON_ITEM),
errmsg("JSON path expression in JSON_VALUE must return single scalar item")));
}
res = JsonValueListHead(&found);
if (res->type == jbvBinary && JsonContainerIsScalar(res->val.binary.data))
JsonbExtractScalar(res->val.binary.data, res);
/* JSON_VALUE expects to get only scalars. */
if (!IsAJsonbScalar(res))
{
if (error)
{
*error = true;
return NULL;
}
if (column_name)
ereport(ERROR,
(errcode(ERRCODE_SQL_JSON_SCALAR_REQUIRED),
errmsg("JSON path expression for column \"%s\" must return single scalar item",
column_name)));
else
ereport(ERROR,
(errcode(ERRCODE_SQL_JSON_SCALAR_REQUIRED),
errmsg("JSON path expression in JSON_VALUE must return single scalar item")));
}
/*
* Sanity-checks and returns the opaque JsonTableExecContext from the
* given executor state struct.
*/
static inline JsonTableExecContext *
GetJsonTableExecContext(TableFuncScanState *state, const char *fname)
{
JsonTableExecContext *result;
if (!IsA(state, TableFuncScanState))
elog(ERROR, "%s called with invalid TableFuncScanState", fname);
result = (JsonTableExecContext *) state->opaque;
if (result->magic != JSON_TABLE_EXEC_CONTEXT_MAGIC)
elog(ERROR, "%s called with invalid TableFuncScanState", fname);
return result;
}
/*
* JsonTableInitOpaque
* Fill in TableFuncScanState->opaque for processing JSON_TABLE
*
* This initializes the PASSING arguments and the JsonTablePlanState for
* JsonTablePlan given in TableFunc.
*/
static void
JsonTableInitOpaque(TableFuncScanState *state, int natts)
{
JsonTableExecContext *cxt;
PlanState *ps = &state->ss.ps;
TableFuncScan *tfs = castNode(TableFuncScan, ps->plan);
TableFunc *tf = tfs->tablefunc;
JsonTablePlan *rootplan = (JsonTablePlan *) tf->plan;
JsonExpr *je = castNode(JsonExpr, tf->docexpr);
List *args = NIL;
/*
* Evaluate JSON_TABLE() PASSING arguments to be passed to the jsonpath
* executor via JsonPathVariables.
*/
if (state->passingvalexprs)
{
ListCell *exprlc;
ListCell *namelc;
/*
* Evaluate the expression and save the value to be returned by
* GetJsonPathVar().
*/
var->value = ExecEvalExpr(state, ps->ps_ExprContext,
&var->isnull);
/*
* Initialize plan for the root path and, recursively, also any child
* plans that compute the NESTED paths.
*/
cxt->rootplanstate = JsonTableInitPlan(cxt, rootplan, NULL, args,
CurrentMemoryContext);
/*
* JsonTableInitPlan
* Initialize information for evaluating jsonpath in the given
* JsonTablePlan and, recursively, in any child plans
*/
static JsonTablePlanState *
JsonTableInitPlan(JsonTableExecContext *cxt, JsonTablePlan *plan,
JsonTablePlanState *parentstate,
List *args, MemoryContext mcxt)
{
JsonTablePlanState *planstate = palloc0(sizeof(*planstate));
/*
* Evaluate a JsonTablePlan's jsonpath to get a new row pattern from
* the given context item
*/
static void
JsonTableResetRowPattern(JsonTablePlanState *planstate, Datum item)
{
JsonTablePathScan *scan = castNode(JsonTablePathScan, planstate->plan);
MemoryContext oldcxt;
JsonPathExecResult res;
Jsonb *js = (Jsonb *) DatumGetJsonbP(item);
JsonValueListClear(&planstate->found);
MemoryContextResetOnly(planstate->mcxt);
oldcxt = MemoryContextSwitchTo(planstate->mcxt);
res = executeJsonPath(planstate->path, planstate->args,
GetJsonPathVar, CountJsonPathVars,
js, scan->errorOnError,
&planstate->found,
true);
MemoryContextSwitchTo(oldcxt);
if (jperIsError(res))
{
Assert(!scan->errorOnError);
JsonValueListClear(&planstate->found);
}
/* Reset plan iterator to the beginning of the item list */
JsonValueListInitIterator(&planstate->found, &planstate->iter);
planstate->current.value = PointerGetDatum(NULL);
planstate->current.isnull = true;
planstate->ordinal = 0;
}
/*
* Fetch next row from a JsonTablePlan.
*
* Returns false if the plan has run out of rows, true otherwise.
*/
static bool
JsonTablePlanNextRow(JsonTablePlanState *planstate)
{
if (IsA(planstate->plan, JsonTablePathScan))
return JsonTablePlanScanNextRow(planstate);
else if (IsA(planstate->plan, JsonTableSiblingJoin))
return JsonTablePlanJoinNextRow(planstate);
else
elog(ERROR, "invalid JsonTablePlan %d", (int) planstate->plan->type);
/*
* Fetch next row from a JsonTablePlan's path evaluation result and from
* any child nested path(s).
*
* Returns true if any of the paths (this or the nested) has more rows to
* return.
*
* By fetching the nested path(s)'s rows based on the parent row at each
* level, this essentially joins the rows of different levels. If a nested
* path at a given level has no matching rows, the columns of that level will
* compute to NULL, making it an OUTER join.
*/
static bool
JsonTablePlanScanNextRow(JsonTablePlanState *planstate)
{
JsonbValue *jbv;
MemoryContext oldcxt;
/*
* If planstate already has an active row and there is a nested plan,
* check if it has an active row to join with the former.
*/
if (!planstate->current.isnull)
{
if (planstate->nested && JsonTablePlanNextRow(planstate->nested))
return true;
}
/* Fetch new row from the list of found values to set as active. */
jbv = JsonValueListNext(&planstate->found, &planstate->iter);
/* End of list? */
if (jbv == NULL)
{
planstate->current.value = PointerGetDatum(NULL);
planstate->current.isnull = true;
return false;
}
/*
* Set current row item for subsequent JsonTableGetValue() calls for
* evaluating individual columns.
*/
oldcxt = MemoryContextSwitchTo(planstate->mcxt);
planstate->current.value = JsonbPGetDatum(JsonbValueToJsonb(jbv));
planstate->current.isnull = false;
MemoryContextSwitchTo(oldcxt);
/* Next row! */
planstate->ordinal++;
/* Process nested plan(s), if any. */
if (planstate->nested)
{
/* Re-evaluate the nested path using the above parent row. */
JsonTableResetNestedPlan(planstate->nested);
/*
* Now fetch the nested plan's current row to be joined against the
* parent row. Any further nested plans' paths will be re-evaluated
* recursively, level at a time, after setting each nested plan's
* current row.
*/
(void) JsonTablePlanNextRow(planstate->nested);
}
/* There are more rows. */
return true;
}
/*
* Re-evaluate the row pattern of a nested plan using the new parent row
* pattern.
*/
static void
JsonTableResetNestedPlan(JsonTablePlanState *planstate)
{
/* This better be a child plan. */
Assert(planstate->parent != NULL);
if (IsA(planstate->plan, JsonTablePathScan))
{
JsonTablePlanState *parent = planstate->parent;
if (!parent->current.isnull)
JsonTableResetRowPattern(planstate, parent->current.value);
/*
* If this plan itself has a child nested plan, it will be reset when
* the caller calls JsonTablePlanNextRow() on this plan.
*/
}
else if (IsA(planstate->plan, JsonTableSiblingJoin))
{
JsonTableResetNestedPlan(planstate->left);
JsonTableResetNestedPlan(planstate->right);
}
}
/*
* Fetch the next row from a JsonTableSiblingJoin.
*
* This is essentially a UNION between the rows from left and right siblings.
*/
static bool
JsonTablePlanJoinNextRow(JsonTablePlanState *planstate)
{
/* Fetch row from left sibling. */
if (!JsonTablePlanNextRow(planstate->left))
{
/*
* Left sibling ran out of rows, so start fetching from the right
* sibling.
*/
if (!JsonTablePlanNextRow(planstate->right))
{
/* Right sibling ran out of rows too, so there are no more rows. */
return false;
}
}
return true;
}
/*
* JsonTableFetchRow
* Prepare the next "current" row for upcoming GetValue calls.
*
* Returns false if no more rows can be returned.
*/
static bool
JsonTableFetchRow(TableFuncScanState *state)
{
JsonTableExecContext *cxt =
GetJsonTableExecContext(state, "JsonTableFetchRow");
/*
* JsonTableGetValue
* Return the value for column number 'colnum' for the current row.
*
* This leaks memory, so be sure to reset often the context in which it's
* called.
*/
static Datum
JsonTableGetValue(TableFuncScanState *state, int colnum,
Oid typid, int32 typmod, bool *isnull)
{
JsonTableExecContext *cxt =
GetJsonTableExecContext(state, "JsonTableGetValue");
ExprContext *econtext = state->ss.ps.ps_ExprContext;
ExprState *estate = list_nth(state->colvalexprs, colnum);
JsonTablePlanState *planstate = cxt->colplanstates[colnum];
JsonTablePlanRowSource *current = &planstate->current;
Datum result;
/* Row pattern value is NULL */
if (current->isnull)
{
result = (Datum) 0;
*isnull = true;
}
/* Evaluate JsonExpr. */
else if (estate)
{
Datum saved_caseValue = econtext->caseValue_datum;
bool saved_caseIsNull = econtext->caseValue_isNull;
/* Pass the row pattern value via CaseTestExpr. */
econtext->caseValue_datum = current->value;
econtext->caseValue_isNull = false;
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.123Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-10-12)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.