1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-07 00:36:50 +03:00

Revert SQL/JSON features

The reverts the following and makes some associated cleanups:

    commit f79b803dc: Common SQL/JSON clauses
    commit f4fb45d15: SQL/JSON constructors
    commit 5f0adec25: Make STRING an unreserved_keyword.
    commit 33a377608: IS JSON predicate
    commit 1a36bc9db: SQL/JSON query functions
    commit 606948b05: SQL JSON functions
    commit 49082c2cc: RETURNING clause for JSON() and JSON_SCALAR()
    commit 4e34747c8: JSON_TABLE
    commit fadb48b00: PLAN clauses for JSON_TABLE
    commit 2ef6f11b0: Reduce running time of jsonb_sqljson test
    commit 14d3f24fa: Further improve jsonb_sqljson parallel test
    commit a6baa4bad: Documentation for SQL/JSON features
    commit b46bcf7a4: Improve readability of SQL/JSON documentation.
    commit 112fdb352: Fix finalization for json_objectagg and friends
    commit fcdb35c32: Fix transformJsonBehavior
    commit 4cd8717af: Improve a couple of sql/json error messages
    commit f7a605f63: Small cleanups in SQL/JSON code
    commit 9c3d25e17: Fix JSON_OBJECTAGG uniquefying bug
    commit a79153b7a: Claim SQL standard compliance for SQL/JSON features
    commit a1e7616d6: Rework SQL/JSON documentation
    commit 8d9f9634e: Fix errors in copyfuncs/equalfuncs support for JSON node types.
    commit 3c633f32b: Only allow returning string types or bytea from json_serialize
    commit 67b26703b: expression eval: Fix EEOP_JSON_CONSTRUCTOR and EEOP_JSONEXPR size.

The release notes are also adjusted.

Backpatch to release 15.

Discussion: https://postgr.es/m/40d2c882-bcac-19a9-754d-4299e1d87ac7@postgresql.org
This commit is contained in:
Andrew Dunstan
2022-09-01 17:07:14 -04:00
parent 90247e742f
commit 2f2b18bd3f
60 changed files with 348 additions and 14893 deletions

View File

@ -57,31 +57,22 @@
#include "postgres.h"
#include "access/heaptoast.h"
#include "access/xact.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
#include "commands/sequence.h"
#include "executor/execExpr.h"
#include "executor/nodeSubplan.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "parser/parsetree.h"
#include "parser/parse_expr.h"
#include "pgstat.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/date.h"
#include "utils/datum.h"
#include "utils/expandedrecord.h"
#include "utils/json.h"
#include "utils/jsonb.h"
#include "utils/jsonfuncs.h"
#include "utils/jsonpath.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/resowner.h"
#include "utils/timestamp.h"
#include "utils/typcache.h"
#include "utils/xml.h"
@ -488,9 +479,6 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
&&CASE_EEOP_GROUPING_FUNC,
&&CASE_EEOP_WINDOW_FUNC,
&&CASE_EEOP_SUBPLAN,
&&CASE_EEOP_JSON_CONSTRUCTOR,
&&CASE_EEOP_IS_JSON,
&&CASE_EEOP_JSONEXPR,
&&CASE_EEOP_AGG_STRICT_DESERIALIZE,
&&CASE_EEOP_AGG_DESERIALIZE,
&&CASE_EEOP_AGG_STRICT_INPUT_CHECK_ARGS,
@ -1824,27 +1812,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
{
/* too complex for an inline implementation */
ExecEvalAggOrderedTransTuple(state, op, econtext);
EEO_NEXT();
}
EEO_CASE(EEOP_JSON_CONSTRUCTOR)
{
/* too complex for an inline implementation */
ExecEvalJsonConstructor(state, op, econtext);
EEO_NEXT();
}
EEO_CASE(EEOP_IS_JSON)
{
/* too complex for an inline implementation */
ExecEvalJsonIsPredicate(state, op);
EEO_NEXT();
}
EEO_CASE(EEOP_JSONEXPR)
{
/* too complex for an inline implementation */
ExecEvalJson(state, op, econtext);
EEO_NEXT();
}
@ -3972,91 +3940,6 @@ ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
}
}
void
ExecEvalJsonIsPredicate(ExprState *state, ExprEvalStep *op)
{
JsonIsPredicate *pred = op->d.is_json.pred;
Datum js = *op->resvalue;
Oid exprtype;
bool res;
if (*op->resnull)
{
*op->resvalue = BoolGetDatum(false);
return;
}
exprtype = exprType(pred->expr);
if (exprtype == TEXTOID || exprtype == JSONOID)
{
text *json = DatumGetTextP(js);
if (pred->item_type == JS_TYPE_ANY)
res = true;
else
{
switch (json_get_first_token(json, false))
{
case JSON_TOKEN_OBJECT_START:
res = pred->item_type == JS_TYPE_OBJECT;
break;
case JSON_TOKEN_ARRAY_START:
res = pred->item_type == JS_TYPE_ARRAY;
break;
case JSON_TOKEN_STRING:
case JSON_TOKEN_NUMBER:
case JSON_TOKEN_TRUE:
case JSON_TOKEN_FALSE:
case JSON_TOKEN_NULL:
res = pred->item_type == JS_TYPE_SCALAR;
break;
default:
res = false;
break;
}
}
/*
* Do full parsing pass only for uniqueness check or for JSON text
* validation.
*/
if (res && (pred->unique_keys || exprtype == TEXTOID))
res = json_validate(json, pred->unique_keys, false);
}
else if (exprtype == JSONBOID)
{
if (pred->item_type == JS_TYPE_ANY)
res = true;
else
{
Jsonb *jb = DatumGetJsonbP(js);
switch (pred->item_type)
{
case JS_TYPE_OBJECT:
res = JB_ROOT_IS_OBJECT(jb);
break;
case JS_TYPE_ARRAY:
res = JB_ROOT_IS_ARRAY(jb) && !JB_ROOT_IS_SCALAR(jb);
break;
case JS_TYPE_SCALAR:
res = JB_ROOT_IS_ARRAY(jb) && JB_ROOT_IS_SCALAR(jb);
break;
default:
res = false;
break;
}
}
/* Key uniqueness check is redundant for jsonb */
}
else
res = false;
*op->resvalue = BoolGetDatum(res);
}
/*
* ExecEvalGroupingFunc
*
@ -4619,629 +4502,3 @@ ExecAggPlainTransByRef(AggState *aggstate, AggStatePerTrans pertrans,
MemoryContextSwitchTo(oldContext);
}
/*
* Evaluate a JSON constructor expression.
*/
void
ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
ExprContext *econtext)
{
Datum res;
JsonConstructorExprState *jcstate = op->d.json_constructor.jcstate;
JsonConstructorExpr *ctor = jcstate->constructor;
bool is_jsonb = ctor->returning->format->format_type == JS_FORMAT_JSONB;
bool isnull = false;
if (ctor->type == JSCTOR_JSON_ARRAY)
res = (is_jsonb ?
jsonb_build_array_worker :
json_build_array_worker) (jcstate->nargs,
jcstate->arg_values,
jcstate->arg_nulls,
jcstate->arg_types,
ctor->absent_on_null);
else if (ctor->type == JSCTOR_JSON_OBJECT)
res = (is_jsonb ?
jsonb_build_object_worker :
json_build_object_worker) (jcstate->nargs,
jcstate->arg_values,
jcstate->arg_nulls,
jcstate->arg_types,
ctor->absent_on_null,
ctor->unique);
else if (ctor->type == JSCTOR_JSON_SCALAR)
{
if (jcstate->arg_nulls[0])
{
res = (Datum) 0;
isnull = true;
}
else
{
Datum value = jcstate->arg_values[0];
int category = jcstate->arg_type_cache[0].category;
Oid outfuncid = jcstate->arg_type_cache[0].outfuncid;
if (is_jsonb)
res = to_jsonb_worker(value, category, outfuncid);
else
res = to_json_worker(value, category, outfuncid);
}
}
else if (ctor->type == JSCTOR_JSON_PARSE)
{
if (jcstate->arg_nulls[0])
{
res = (Datum) 0;
isnull = true;
}
else
{
Datum value = jcstate->arg_values[0];
text *js = DatumGetTextP(value);
if (is_jsonb)
res = jsonb_from_text(js, true);
else
{
(void) json_validate(js, true, true);
res = value;
}
}
}
else
{
res = (Datum) 0;
elog(ERROR, "invalid JsonConstructorExpr type %d", ctor->type);
}
*op->resvalue = res;
*op->resnull = isnull;
}
/*
* Evaluate a JSON error/empty behavior result.
*/
static Datum
ExecEvalJsonBehavior(ExprContext *econtext, JsonBehavior *behavior,
ExprState *default_estate, bool *is_null)
{
*is_null = false;
switch (behavior->btype)
{
case JSON_BEHAVIOR_EMPTY_ARRAY:
return JsonbPGetDatum(JsonbMakeEmptyArray());
case JSON_BEHAVIOR_EMPTY_OBJECT:
return JsonbPGetDatum(JsonbMakeEmptyObject());
case JSON_BEHAVIOR_TRUE:
return BoolGetDatum(true);
case JSON_BEHAVIOR_FALSE:
return BoolGetDatum(false);
case JSON_BEHAVIOR_NULL:
case JSON_BEHAVIOR_UNKNOWN:
case JSON_BEHAVIOR_EMPTY:
*is_null = true;
return (Datum) 0;
case JSON_BEHAVIOR_DEFAULT:
return ExecEvalExpr(default_estate, econtext, is_null);
default:
elog(ERROR, "unrecognized SQL/JSON behavior %d", behavior->btype);
return (Datum) 0;
}
}
/*
* Evaluate a coercion of a JSON item to the target type.
*/
static Datum
ExecEvalJsonExprCoercion(ExprEvalStep *op, ExprContext *econtext,
Datum res, bool *isNull, void *p, bool *error)
{
ExprState *estate = p;
JsonExprState *jsestate;
if (estate) /* coerce using specified expression */
return ExecEvalExpr(estate, econtext, isNull);
jsestate = op->d.jsonexpr.jsestate;
if (jsestate->jsexpr->op != JSON_EXISTS_OP)
{
JsonCoercion *coercion = jsestate->jsexpr->result_coercion;
JsonExpr *jexpr = jsestate->jsexpr;
Jsonb *jb = *isNull ? NULL : DatumGetJsonbP(res);
if ((coercion && coercion->via_io) ||
(jexpr->omit_quotes && !*isNull &&
JB_ROOT_IS_SCALAR(jb)))
{
/* strip quotes and call typinput function */
char *str = *isNull ? NULL : JsonbUnquote(jb);
return InputFunctionCall(&jsestate->input.func, str,
jsestate->input.typioparam,
jexpr->returning->typmod);
}
else if (coercion && coercion->via_populate)
return json_populate_type(res, JSONBOID,
jexpr->returning->typid,
jexpr->returning->typmod,
&jsestate->cache,
econtext->ecxt_per_query_memory,
isNull);
}
if (jsestate->result_expr)
{
jsestate->res_expr->value = res;
jsestate->res_expr->isnull = *isNull;
res = ExecEvalExpr(jsestate->result_expr, econtext, isNull);
}
return res;
}
/*
* Evaluate a JSON path variable caching computed value.
*/
int
EvalJsonPathVar(void *cxt, char *varName, int varNameLen,
JsonbValue *val, JsonbValue *baseObject)
{
JsonPathVariableEvalContext *var = NULL;
List *vars = cxt;
ListCell *lc;
int id = 1;
if (!varName)
return list_length(vars);
foreach(lc, vars)
{
var = lfirst(lc);
if (!strncmp(var->name, varName, varNameLen))
break;
var = NULL;
id++;
}
if (!var)
return -1;
if (!var->evaluated)
{
MemoryContext oldcxt = var->mcxt ?
MemoryContextSwitchTo(var->mcxt) : NULL;
var->value = ExecEvalExpr(var->estate, var->econtext, &var->isnull);
var->evaluated = true;
if (oldcxt)
MemoryContextSwitchTo(oldcxt);
}
if (var->isnull)
{
val->type = jbvNull;
return 0;
}
JsonItemFromDatum(var->value, var->typid, var->typmod, val);
*baseObject = *val;
return id;
}
/*
* Prepare SQL/JSON item coercion to the output type. Returned a datum of the
* corresponding SQL type and a pointer to the coercion state.
*/
Datum
ExecPrepareJsonItemCoercion(JsonbValue *item,
JsonReturning *returning,
struct JsonCoercionsState *coercions,
struct JsonCoercionState **pcoercion)
{
struct JsonCoercionState *coercion;
Datum res;
JsonbValue buf;
if (item->type == jbvBinary &&
JsonContainerIsScalar(item->val.binary.data))
{
bool res PG_USED_FOR_ASSERTS_ONLY;
res = JsonbExtractScalar(item->val.binary.data, &buf);
item = &buf;
Assert(res);
}
/* get coercion state reference and datum of the corresponding SQL type */
switch (item->type)
{
case jbvNull:
coercion = &coercions->null;
res = (Datum) 0;
break;
case jbvString:
coercion = &coercions->string;
res = PointerGetDatum(cstring_to_text_with_len(item->val.string.val,
item->val.string.len));
break;
case jbvNumeric:
coercion = &coercions->numeric;
res = NumericGetDatum(item->val.numeric);
break;
case jbvBool:
coercion = &coercions->boolean;
res = BoolGetDatum(item->val.boolean);
break;
case jbvDatetime:
res = item->val.datetime.value;
switch (item->val.datetime.typid)
{
case DATEOID:
coercion = &coercions->date;
break;
case TIMEOID:
coercion = &coercions->time;
break;
case TIMETZOID:
coercion = &coercions->timetz;
break;
case TIMESTAMPOID:
coercion = &coercions->timestamp;
break;
case TIMESTAMPTZOID:
coercion = &coercions->timestamptz;
break;
default:
elog(ERROR, "unexpected jsonb datetime type oid %u",
item->val.datetime.typid);
return (Datum) 0;
}
break;
case jbvArray:
case jbvObject:
case jbvBinary:
coercion = &coercions->composite;
res = JsonbPGetDatum(JsonbValueToJsonb(item));
break;
default:
elog(ERROR, "unexpected jsonb value type %d", item->type);
return (Datum) 0;
}
*pcoercion = coercion;
return res;
}
typedef Datum (*JsonFunc) (ExprEvalStep *op, ExprContext *econtext,
Datum item, bool *resnull, void *p, bool *error);
static Datum
ExecEvalJsonExprSubtrans(JsonFunc func, ExprEvalStep *op,
ExprContext *econtext,
Datum res, bool *resnull,
void *p, bool *error, bool subtrans)
{
MemoryContext oldcontext;
ResourceOwner oldowner;
if (!subtrans)
/* No need to use subtransactions. */
return func(op, econtext, res, resnull, p, error);
/*
* We should catch exceptions of category ERRCODE_DATA_EXCEPTION and
* execute the corresponding ON ERROR behavior then.
*/
oldcontext = CurrentMemoryContext;
oldowner = CurrentResourceOwner;
Assert(error);
BeginInternalSubTransaction(NULL);
/* Want to execute expressions inside function's memory context */
MemoryContextSwitchTo(oldcontext);
PG_TRY();
{
res = func(op, econtext, res, resnull, p, error);
/* Commit the inner transaction, return to outer xact context */
ReleaseCurrentSubTransaction();
MemoryContextSwitchTo(oldcontext);
CurrentResourceOwner = oldowner;
}
PG_CATCH();
{
ErrorData *edata;
int ecategory;
/* Save error info in oldcontext */
MemoryContextSwitchTo(oldcontext);
edata = CopyErrorData();
FlushErrorState();
/* Abort the inner transaction */
RollbackAndReleaseCurrentSubTransaction();
MemoryContextSwitchTo(oldcontext);
CurrentResourceOwner = oldowner;
ecategory = ERRCODE_TO_CATEGORY(edata->sqlerrcode);
if (ecategory != ERRCODE_DATA_EXCEPTION && /* jsonpath and other data
* errors */
ecategory != ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION) /* domain errors */
ReThrowError(edata);
res = (Datum) 0;
*error = true;
}
PG_END_TRY();
return res;
}
typedef struct
{
JsonPath *path;
bool *error;
bool coercionInSubtrans;
} ExecEvalJsonExprContext;
static Datum
ExecEvalJsonExpr(ExprEvalStep *op, ExprContext *econtext,
Datum item, bool *resnull, void *pcxt,
bool *error)
{
ExecEvalJsonExprContext *cxt = pcxt;
JsonPath *path = cxt->path;
JsonExprState *jsestate = op->d.jsonexpr.jsestate;
JsonExpr *jexpr = jsestate->jsexpr;
ExprState *estate = NULL;
bool empty = false;
Datum res = (Datum) 0;
switch (jexpr->op)
{
case JSON_QUERY_OP:
res = JsonPathQuery(item, path, jexpr->wrapper, &empty, error,
jsestate->args);
if (error && *error)
{
*resnull = true;
return (Datum) 0;
}
*resnull = !DatumGetPointer(res);
break;
case JSON_VALUE_OP:
{
struct JsonCoercionState *jcstate;
JsonbValue *jbv = JsonPathValue(item, path, &empty, error,
jsestate->args);
if (error && *error)
return (Datum) 0;
if (!jbv) /* NULL or empty */
break;
Assert(!empty);
*resnull = false;
/* coerce scalar item to the output type */
if (jexpr->returning->typid == JSONOID ||
jexpr->returning->typid == JSONBOID)
{
/* Use result coercion from json[b] to the output type */
res = JsonbPGetDatum(JsonbValueToJsonb(jbv));
break;
}
/* Use coercion from SQL/JSON item type to the output type */
res = ExecPrepareJsonItemCoercion(jbv,
jsestate->jsexpr->returning,
&jsestate->coercions,
&jcstate);
if (jcstate->coercion &&
(jcstate->coercion->via_io ||
jcstate->coercion->via_populate))
{
if (error)
{
*error = true;
return (Datum) 0;
}
/*
* Coercion via I/O means here that the cast to the target
* type simply does not exist.
*/
ereport(ERROR,
(errcode(ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE),
errmsg("SQL/JSON item cannot be cast to target type")));
}
else if (!jcstate->estate)
return res; /* no coercion */
/* coerce using specific expression */
estate = jcstate->estate;
jsestate->coercion_expr->value = res;
jsestate->coercion_expr->isnull = *resnull;
break;
}
case JSON_EXISTS_OP:
{
bool exists = JsonPathExists(item, path,
jsestate->args,
error);
*resnull = error && *error;
res = BoolGetDatum(exists);
if (!jsestate->result_expr)
return res;
/* coerce using result expression */
estate = jsestate->result_expr;
jsestate->res_expr->value = res;
jsestate->res_expr->isnull = *resnull;
break;
}
case JSON_TABLE_OP:
*resnull = false;
return item;
default:
elog(ERROR, "unrecognized SQL/JSON expression op %d", jexpr->op);
return (Datum) 0;
}
if (empty)
{
Assert(jexpr->on_empty); /* it is not JSON_EXISTS */
if (jexpr->on_empty->btype == JSON_BEHAVIOR_ERROR)
{
if (error)
{
*error = true;
return (Datum) 0;
}
ereport(ERROR,
(errcode(ERRCODE_NO_SQL_JSON_ITEM),
errmsg("no SQL/JSON item")));
}
if (jexpr->on_empty->btype == JSON_BEHAVIOR_DEFAULT)
/*
* Execute DEFAULT expression as a coercion expression, because
* its result is already coerced to the target type.
*/
estate = jsestate->default_on_empty;
else
/* Execute ON EMPTY behavior */
res = ExecEvalJsonBehavior(econtext, jexpr->on_empty,
jsestate->default_on_empty,
resnull);
}
return ExecEvalJsonExprSubtrans(ExecEvalJsonExprCoercion, op, econtext,
res, resnull, estate, error,
cxt->coercionInSubtrans);
}
bool
ExecEvalJsonNeedsSubTransaction(JsonExpr *jsexpr,
struct JsonCoercionsState *coercions)
{
if (jsexpr->on_error->btype == JSON_BEHAVIOR_ERROR)
return false;
if (jsexpr->op == JSON_EXISTS_OP && !jsexpr->result_coercion)
return false;
if (!coercions)
return true;
return false;
}
/* ----------------------------------------------------------------
* ExecEvalJson
* ----------------------------------------------------------------
*/
void
ExecEvalJson(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
{
ExecEvalJsonExprContext cxt;
JsonExprState *jsestate = op->d.jsonexpr.jsestate;
JsonExpr *jexpr = jsestate->jsexpr;
Datum item;
Datum res = (Datum) 0;
JsonPath *path;
ListCell *lc;
bool error = false;
bool needSubtrans;
bool throwErrors = jexpr->on_error->btype == JSON_BEHAVIOR_ERROR;
*op->resnull = true; /* until we get a result */
*op->resvalue = (Datum) 0;
if (jsestate->formatted_expr->isnull || jsestate->pathspec->isnull)
{
/* execute domain checks for NULLs */
(void) ExecEvalJsonExprCoercion(op, econtext, res, op->resnull,
NULL, NULL);
Assert(*op->resnull);
return;
}
item = jsestate->formatted_expr->value;
path = DatumGetJsonPathP(jsestate->pathspec->value);
/* reset JSON path variable contexts */
foreach(lc, jsestate->args)
{
JsonPathVariableEvalContext *var = lfirst(lc);
var->econtext = econtext;
var->evaluated = false;
}
needSubtrans = ExecEvalJsonNeedsSubTransaction(jexpr, &jsestate->coercions);
cxt.path = path;
cxt.error = throwErrors ? NULL : &error;
cxt.coercionInSubtrans = !needSubtrans && !throwErrors;
Assert(!needSubtrans || cxt.error);
res = ExecEvalJsonExprSubtrans(ExecEvalJsonExpr, op, econtext, item,
op->resnull, &cxt, cxt.error,
needSubtrans);
if (error)
{
/* Execute ON ERROR behavior */
res = ExecEvalJsonBehavior(econtext, jexpr->on_error,
jsestate->default_on_error,
op->resnull);
/* result is already coerced in DEFAULT behavior case */
if (jexpr->on_error->btype != JSON_BEHAVIOR_DEFAULT)
res = ExecEvalJsonExprCoercion(op, econtext, res,
op->resnull,
NULL, NULL);
}
*op->resvalue = res;
}