From 7e9278c86d62e747bca862b6bc8a11e31fcd36ab Mon Sep 17 00:00:00 2001 From: Alexandra Wang Date: Tue, 8 Jul 2025 22:18:07 -0700 Subject: [PATCH v13 5/7] Implement read-only dot notation for jsonb This patch introduces JSONB member access using dot notation that aligns with the JSON simplified accessor specified in SQL:2023. Examples: -- Setup create table t(x int, y jsonb); insert into t select 1, '{"a": 1, "b": 42}'::jsonb; insert into t select 1, '{"a": 2, "b": {"c": 42}}'::jsonb; insert into t select 1, '{"a": 3, "b": {"c": "42"}, "d":[11, 12]}'::jsonb; -- Existing syntax in PostgreSQL that predates the SQL standard: select (t.y)->'b' from t; select (t.y)->'b'->'c' from t; select (t.y)->'d'->0 from t; -- JSON simplified accessor specified by the SQL standard: select (t.y).b from t; select (t.y).b.c from t; select (t.y).d[0] from t; The SQL standard states that simplified access is equivalent to: JSON_QUERY (VEP, 'lax $.JC' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) where: VEP = JC = For example, the JSON_QUERY equivalents of the above queries are: select json_query(y, 'lax $.b' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t; select json_query(y, 'lax $.b.c' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t; select json_query(y, 'lax $.d[0]' WITH CONDITIONAL ARRAY WRAPPER NULL ON EMPTY NULL ON ERROR) from t; Implementation details: Extends the existing container subscripting interface to support container-specific information, specifically a JSONPath expression for jsonb. During query transformation, if dot-notation is present, constructs a JSONPath expression representing the access chain. During execution, if a JSONPath expression is present in JsonbSubWorkspace, executes it via JsonPathQuery(). Does not transform accessors directly into JSON_QUERY during transformation to preserve the original query structure for EXPLAIN and CREATE VIEW. Co-authored-by: Nikita Glukhov Co-authored-by: Alexandra Wang Reviewed-by: Andrew Dunstan Reviewed-by: Matheus Alcantara Reviewed-by: Mark Dilger Reviewed-by: Jian He Reviewed-by: Vik Fearing Reviewed-by: Nikita Malakhov Reviewed-by: Peter Eisentraut Tested-by: Jelte Fennema-Nio --- src/backend/catalog/sql_features.txt | 4 +- src/backend/executor/execExpr.c | 76 ++-- src/backend/nodes/nodeFuncs.c | 12 + src/backend/utils/adt/jsonbsubs.c | 352 +++++++++++++-- src/backend/utils/adt/ruleutils.c | 43 +- src/include/nodes/primnodes.h | 54 ++- .../ecpg/test/expected/sql-sqljson.c | 112 ++++- .../ecpg/test/expected/sql-sqljson.stderr | 100 +++++ .../ecpg/test/expected/sql-sqljson.stdout | 7 + src/interfaces/ecpg/test/sql/sqljson.pgc | 33 ++ src/test/regress/expected/jsonb.out | 401 +++++++++++++++++- src/test/regress/sql/jsonb.sql | 111 ++++- src/tools/pgindent/typedefs.list | 1 + 13 files changed, 1206 insertions(+), 100 deletions(-) diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt index ebe85337c28..457e993305e 100644 --- a/src/backend/catalog/sql_features.txt +++ b/src/backend/catalog/sql_features.txt @@ -568,8 +568,8 @@ T838 JSON_TABLE: PLAN DEFAULT clause NO T839 Formatted cast of datetimes to/from character strings NO T840 Hex integer literals in SQL/JSON path language YES T851 SQL/JSON: optional keywords for default syntax YES -T860 SQL/JSON simplified accessor: column reference only NO -T861 SQL/JSON simplified accessor: case-sensitive JSON member accessor NO +T860 SQL/JSON simplified accessor: column reference only YES +T861 SQL/JSON simplified accessor: case-sensitive JSON member accessor YES T862 SQL/JSON simplified accessor: wildcard member accessor NO T863 SQL/JSON simplified accessor: single-quoted string literal as member accessor NO T864 SQL/JSON simplified accessor NO diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index f1569879b52..75e57ca0c79 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -3320,50 +3320,52 @@ ExecInitSubscriptingRef(ExprEvalStep *scratch, SubscriptingRef *sbsref, state->steps_len - 1); } - /* Evaluate upper subscripts */ - i = 0; - foreach(lc, sbsref->refupperindexpr) + /* Evaluate upper subscripts, unless refjsonbpath is used for execution */ + if (!sbsref->refjsonbpath) { - Expr *e = (Expr *) lfirst(lc); - - /* When slicing, individual subscript bounds can be omitted */ - if (!e) - { - sbsrefstate->upperprovided[i] = false; - sbsrefstate->upperindexnull[i] = true; - } - else - { - sbsrefstate->upperprovided[i] = true; - /* Each subscript is evaluated into appropriate array entry */ - ExecInitExprRec(e, state, - &sbsrefstate->upperindex[i], - &sbsrefstate->upperindexnull[i]); + i = 0; + foreach(lc, sbsref->refupperindexpr) { + Expr *e = (Expr *) lfirst(lc); + + /* When slicing, individual subscript bounds can be omitted */ + if (!e) { + sbsrefstate->upperprovided[i] = false; + sbsrefstate->upperindexnull[i] = true; + } else { + sbsrefstate->upperprovided[i] = true; + /* Each subscript is evaluated into appropriate array entry */ + ExecInitExprRec(e, state, + &sbsrefstate->upperindex[i], + &sbsrefstate->upperindexnull[i]); + } + i++; } - i++; } - /* Evaluate lower subscripts similarly */ - i = 0; - foreach(lc, sbsref->reflowerindexpr) + /* Evaluate lower subscripts similarly, unless refjsonbpath is used for execution */ + if (!sbsref->refjsonbpath) { - Expr *e = (Expr *) lfirst(lc); - - /* When slicing, individual subscript bounds can be omitted */ - if (!e) + i = 0; + foreach(lc, sbsref->reflowerindexpr) { - sbsrefstate->lowerprovided[i] = false; - sbsrefstate->lowerindexnull[i] = true; - } - else - { - sbsrefstate->lowerprovided[i] = true; - /* Each subscript is evaluated into appropriate array entry */ - ExecInitExprRec(e, state, - &sbsrefstate->lowerindex[i], - &sbsrefstate->lowerindexnull[i]); + Expr *e = (Expr *) lfirst(lc); + + /* When slicing, individual subscript bounds can be omitted */ + if (!e) + { + sbsrefstate->lowerprovided[i] = false; + sbsrefstate->lowerindexnull[i] = true; + } + else + { + sbsrefstate->lowerprovided[i] = true; + /* Each subscript is evaluated into appropriate array entry */ + ExecInitExprRec(e, state, + &sbsrefstate->lowerindex[i], + &sbsrefstate->lowerindexnull[i]); + } + i++; } - i++; } /* SBSREF_SUBSCRIPTS checks and converts all the subscripts at once */ diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 7bc823507f1..d1bd575d9bd 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -284,6 +284,13 @@ exprType(const Node *expr) case T_PlaceHolderVar: type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr); break; + case T_FieldAccessorExpr: + /* + * FieldAccessorExpr is not evaluable. Treat it as TEXT for collation, + * deparsing, and similar purposes, since it represents a JSON field name. + */ + type = TEXTOID; + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); type = InvalidOid; /* keep compiler quiet */ @@ -1146,6 +1153,9 @@ exprSetCollation(Node *expr, Oid collation) case T_MergeSupportFunc: ((MergeSupportFunc *) expr)->msfcollid = collation; break; + case T_FieldAccessorExpr: + ((FieldAccessorExpr *) expr)->faecollid = collation; + break; case T_SubscriptingRef: ((SubscriptingRef *) expr)->refcollid = collation; break; @@ -2129,6 +2139,7 @@ expression_tree_walker_impl(Node *node, case T_SortGroupClause: case T_CTESearchClause: case T_MergeSupportFunc: + case T_FieldAccessorExpr: /* primitive node types with no expression subnodes */ break; case T_WithCheckOption: @@ -3008,6 +3019,7 @@ expression_tree_mutator_impl(Node *node, case T_SortGroupClause: case T_CTESearchClause: case T_MergeSupportFunc: + case T_FieldAccessorExpr: return copyObject(node); case T_WithCheckOption: { diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c index f944d1544ca..5808383db26 100644 --- a/src/backend/utils/adt/jsonbsubs.c +++ b/src/backend/utils/adt/jsonbsubs.c @@ -15,21 +15,30 @@ #include "postgres.h" #include "executor/execExpr.h" +#include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "nodes/subscripting.h" #include "parser/parse_coerce.h" #include "parser/parse_expr.h" #include "utils/builtins.h" #include "utils/jsonb.h" +#include "utils/jsonpath.h" -/* SubscriptingRefState.workspace for jsonb subscripting execution */ +/* + * SubscriptingRefState.workspace for generic jsonb subscripting execution. + * + * Stores state for both jsonb simple subscripting and dot notation access. + * Dot notation additionally uses `jsonpath` for JsonPath evaluation. + */ typedef struct JsonbSubWorkspace { bool expectArray; /* jsonb root is expected to be an array */ Oid *indexOid; /* OID of coerced subscript expression, could * be only integer or text */ Datum *index; /* Subscript values in Datum format */ + JsonPath *jsonpath; /* JsonPath for dot notation execution via + * JsonPathQuery() */ } JsonbSubWorkspace; static Node * @@ -96,6 +105,255 @@ coerce_jsonpath_subscript_to_int4_or_text(ParseState *pstate, Node *subExpr) return subExpr; } +/* + * During transformation, determine whether to build a JsonPath + * for JsonPathQuery() execution. + * + * JsonPath is needed if the indirection list includes: + * - String-based access (dot notation) + * - Slice-based subscripting + * + * Otherwise, simple jsonb subscripting is enough. + */ +static bool +jsonb_check_jsonpath_needed(List *indirection) +{ + ListCell *lc; + + foreach(lc, indirection) + { + Node *accessor = lfirst(lc); + + if (IsA(accessor, String)) + return true; + else + { + Assert(IsA(accessor, A_Indices)); + + if (castNode(A_Indices, accessor)->is_slice) + return true; + } + } + + return false; +} + +/* + * Helper functions for constructing JsonPath expressions. + * + * The make_jsonpath_item_* functions create various types of JsonPathParseItem + * nodes, which are used to build JsonPath expressions for jsonb simplified + * accessor. + */ + +static JsonPathParseItem * +make_jsonpath_item(JsonPathItemType type) +{ + JsonPathParseItem *v = palloc(sizeof(*v)); + + v->type = type; + v->next = NULL; + + return v; +} + +/* + * Build a JsonPathParseItem for a constant integer value. + * + * This function constructs a jpiNumeric item for use in JsonPath, + * and appends a matching Const(INT4) node to the given expression list + * for use in EXPLAIN, views, etc. + * + * Parameters: + * - val: integer constant value + * - exprs: list of expression nodes (updated in place) + */ +static JsonPathParseItem * +make_jsonpath_item_int(int32 val, List **exprs) +{ + JsonPathParseItem *jpi = make_jsonpath_item(jpiNumeric); + + jpi->value.numeric = + DatumGetNumeric(DirectFunctionCall1(int4_numeric, Int32GetDatum(val))); + + *exprs = lappend(*exprs, makeConst(INT4OID, -1, InvalidOid, 4, + Int32GetDatum(val), false, true)); + + return jpi; +} + +/* + * Convert a constant integer expression into a JsonPathParseItem. + * + * The input expression must be a non-null constant of type INT4. Returns NULL otherwise. + * The function extracts the value and delegates it to make_jsonpath_item_int(). + * + * Parameters: + * - pstate: parse state context + * - expr: input expression node + * - exprs: list of expression nodes (updated in place) + */ +static JsonPathParseItem * +make_jsonpath_item_expr(ParseState *pstate, Node *expr, List **exprs) +{ + Const *cnst; + + expr = transformExpr(pstate, expr, pstate->p_expr_kind); + + if (IsA(expr, Const)) + { + cnst = (Const *) expr; + if (cnst->consttype == INT4OID && !(cnst->constisnull)) + return make_jsonpath_item_int(DatumGetInt32(cnst->constvalue), exprs); + } + + return NULL; +} + +/* + * Constructs a JsonPath expression from a list of indirections. + * This function is used when jsonb subscripting involves dot notation, + * requiring JsonPath-based evaluation. + * + * The function modifies the indirection list in place, removing processed + * elements as it converts them into JsonPath components, as follows: + * - String keys (dot notation) -> jpiKey items. + * - Array indices -> jpiIndexArray items. + * + * In addition to building the JsonPath expression, this function populates + * the following fields of the given SubscriptingRef: + * - refjsonbpath: the generated JsonPath + * - refupperindexpr: upper index expressions (object keys or array indexes) + * - reflowerindexpr: lower index expressions, remains NIL as slices are not yet supported. + * + * Parameters: + * - pstate: Parse state context. + * - indirection: List of subscripting expressions (modified in-place). + * - sbsref: SubscriptingRef node to update + */ +static void +jsonb_subscript_make_jsonpath(ParseState *pstate, List **indirection, SubscriptingRef *sbsref) +{ + JsonPathParseResult jpres; + JsonPathParseItem *path = make_jsonpath_item(jpiRoot); + ListCell *lc; + Datum jsp; + int pathlen = 0; + + sbsref->refupperindexpr = NIL; + sbsref->reflowerindexpr = NIL; + sbsref->refjsonbpath = NULL; + + jpres.expr = path; + jpres.lax = true; + + foreach(lc, *indirection) + { + Node *accessor = lfirst(lc); + JsonPathParseItem *jpi; + + if (IsA(accessor, String)) + { + char *field = strVal(accessor); + FieldAccessorExpr *accessor_expr; + + jpi = make_jsonpath_item(jpiKey); + jpi->value.string.val = field; + jpi->value.string.len = strlen(field); + + accessor_expr = makeNode(FieldAccessorExpr); + accessor_expr->type = T_FieldAccessorExpr; + accessor_expr->fieldname = field; + + sbsref->refupperindexpr = lappend(sbsref->refupperindexpr, accessor_expr); + } + else if (IsA(accessor, A_Indices)) + { + A_Indices *ai = castNode(A_Indices, accessor); + + jpi = make_jsonpath_item(jpiIndexArray); + jpi->value.array.nelems = 1; + jpi->value.array.elems = palloc(sizeof(jpi->value.array.elems[0])); + + if (ai->is_slice) + { + Node *expr = ai->uidx ? ai->uidx : ai->lidx; + + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("jsonb subscript does not support slices"), + parser_errposition(pstate, exprLocation(expr)))); + } + else + { + JsonPathParseItem *jpi_from = NULL; + + Assert(ai->uidx && !ai->lidx); + jpi_from = make_jsonpath_item_expr(pstate, ai->uidx, &sbsref->refupperindexpr); + if (jpi_from == NULL) + { + /* + * Break out of the loop if the subscript is not a + * non-null integer constant, so that we can fall back to + * jsonb subscripting logic. + * + * This is needed to handle cases with mixed usage of SQL + * standard json simplified accessor syntax and PostgreSQL + * jsonb subscripting syntax, e.g: + * + * select (jb).a['b'].c from jsonb_table; + * + * where dot-notation (.a and .c) is the SQL standard json + * simplified accessor syntax, and the ['b'] subscript is + * the PostgreSQL jsonb subscripting syntax, because 'b' + * is not a non-null constant integer and cannot be used + * for json array access. + * + * In this case, we cannot create a JsonPath item, so we + * break out of the loop and let + * jsonb_subscript_transform() handle this indirection as + * a PostgreSQL jsonb subscript. + */ + pfree(jpi->value.array.elems); + pfree(jpi); + break; + } + + jpi->value.array.elems[0].from = jpi_from; + jpi->value.array.elems[0].to = NULL; + } + } + else + { + /* + * Unexpected node type in indirection list. This should not + * happen with current grammar, but we handle it defensively by + * breaking out of the loop rather than crashing. In case of + * future grammar changes that might introduce new node types, + * this allows us to create a jsonpath from as many indirection + * elements as we can and let transformIndirection() fallback to + * alternative logic to handle the remaining indirection elements. + */ + Assert(false); /* not reachable */ + break; + } + + /* append path item */ + path->next = jpi; + path = jpi; + pathlen++; + } + + if (pathlen == 0) + return; + + *indirection = list_delete_first_n(*indirection, pathlen); + + jsp = jsonPathFromParseResult(&jpres, 0, NULL); + + sbsref->refjsonbpath = (Node *) makeConst(JSONPATHOID, -1, InvalidOid, -1, jsp, false, false); +} + /* * Finish parse analysis of a SubscriptingRef expression for a jsonb. * @@ -112,47 +370,43 @@ jsonb_subscript_transform(SubscriptingRef *sbsref, List *upperIndexpr = NIL; ListCell *idx; + /* Determine the result type of the subscripting operation; always jsonb */ + sbsref->refrestype = JSONBOID; + sbsref->reftypmod = -1; + + if (isSlice || jsonb_check_jsonpath_needed(*indirection)) + { + jsonb_subscript_make_jsonpath(pstate, indirection, sbsref); + if (sbsref->refjsonbpath) + return; + } + /* - * Transform and convert the subscript expressions. Jsonb subscripting - * does not support slices, look only at the upper index. + * We would only reach here if json simplified accessor is not needed, or + * if jsonb_subscript_make_jsonpath() didn't consume any indirection + * element — either way, the first indirection element could not be + * converted into a JsonPath component. This happens when it's a non-slice + * A_Indices with a non-integer upper index. The code below falls back to + * traditional jsonb subscripting for such cases. */ foreach(idx, *indirection) { + Node *i = lfirst(idx); A_Indices *ai; Node *subExpr; if (!IsA(lfirst(idx), A_Indices)) break; - ai = lfirst_node(A_Indices, idx); + ai = castNode(A_Indices, i); - if (isSlice) - { - Node *expr = ai->uidx ? ai->uidx : ai->lidx; + if (ai->is_slice) + break; - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("jsonb subscript does not support slices"), - parser_errposition(pstate, exprLocation(expr)))); - } + Assert(!ai->lidx && ai->uidx); - if (ai->uidx) - { - subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind); - subExpr = coerce_jsonpath_subscript_to_int4_or_text(pstate, subExpr); - } - else - { - /* - * Slice with omitted upper bound. Should not happen as we already - * errored out on slice earlier, but handle this just in case. - */ - Assert(isSlice && ai->is_slice); - ereport(ERROR, - (errcode(ERRCODE_DATATYPE_MISMATCH), - errmsg("jsonb subscript does not support slices"), - parser_errposition(pstate, exprLocation(ai->uidx)))); - } + subExpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind); + subExpr = coerce_jsonpath_subscript_to_int4_or_text(pstate, subExpr); upperIndexpr = lappend(upperIndexpr, subExpr); } @@ -161,10 +415,6 @@ jsonb_subscript_transform(SubscriptingRef *sbsref, sbsref->refupperindexpr = upperIndexpr; sbsref->reflowerindexpr = NIL; - /* Determine the result type of the subscripting operation; always jsonb */ - sbsref->refrestype = JSONBOID; - sbsref->reftypmod = -1; - /* Remove processed elements */ if (upperIndexpr) *indirection = list_delete_first_n(*indirection, list_length(upperIndexpr)); @@ -219,7 +469,7 @@ jsonb_subscript_check_subscripts(ExprState *state, * For jsonb fetch and assign functions we need to provide path in * text format. Convert if it's not already text. */ - if (workspace->indexOid[i] == INT4OID) + if (!workspace->jsonpath && workspace->indexOid[i] == INT4OID) { Datum datum = sbsrefstate->upperindex[i]; char *cs = DatumGetCString(DirectFunctionCall1(int4out, datum)); @@ -247,17 +497,32 @@ jsonb_subscript_fetch(ExprState *state, { SubscriptingRefState *sbsrefstate = op->d.sbsref.state; JsonbSubWorkspace *workspace = (JsonbSubWorkspace *) sbsrefstate->workspace; - Jsonb *jsonbSource; /* Should not get here if source jsonb (or any subscript) is null */ Assert(!(*op->resnull)); - jsonbSource = DatumGetJsonbP(*op->resvalue); - *op->resvalue = jsonb_get_element(jsonbSource, - workspace->index, - sbsrefstate->numupper, - op->resnull, - false); + if (workspace->jsonpath) + { + bool empty = false; + bool error = false; + + *op->resvalue = JsonPathQuery(*op->resvalue, workspace->jsonpath, + JSW_CONDITIONAL, + &empty, &error, NULL, + NULL); + + *op->resnull = empty || error; + } + else + { + Jsonb *jsonbSource = DatumGetJsonbP(*op->resvalue); + + *op->resvalue = jsonb_get_element(jsonbSource, + workspace->index, + sbsrefstate->numupper, + op->resnull, + false); + } } /* @@ -365,7 +630,7 @@ jsonb_exec_setup(const SubscriptingRef *sbsref, { JsonbSubWorkspace *workspace; ListCell *lc; - int nupper = sbsref->refupperindexpr->length; + int nupper = list_length(sbsref->refupperindexpr); char *ptr; /* Allocate type-specific workspace with space for per-subscript data */ @@ -374,6 +639,9 @@ jsonb_exec_setup(const SubscriptingRef *sbsref, workspace->expectArray = false; ptr = ((char *) workspace) + MAXALIGN(sizeof(JsonbSubWorkspace)); + if (sbsref->refjsonbpath) + workspace->jsonpath = DatumGetJsonPathP(castNode(Const, sbsref->refjsonbpath)->constvalue); + /* * This coding assumes sizeof(Datum) >= sizeof(Oid), else we might * misalign the indexOid pointer diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 3d6e6bdbfd2..baa3ae97d57 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -9329,10 +9329,13 @@ get_rule_expr(Node *node, deparse_context *context, * Parenthesize the argument unless it's a simple Var or a * FieldSelect. (In particular, if it's another * SubscriptingRef, we *must* parenthesize to avoid - * confusion.) + * confusion.) Always add parenthesis if JSON simplified + * accessor is used, for now. */ - need_parens = !IsA(sbsref->refexpr, Var) && - !IsA(sbsref->refexpr, FieldSelect); + need_parens = (!IsA(sbsref->refexpr, Var) && + !IsA(sbsref->refexpr, FieldSelect)) || + sbsref->refjsonbpath; + if (need_parens) appendStringInfoChar(buf, '('); get_rule_expr((Node *) sbsref->refexpr, context, showimplicit); @@ -13005,17 +13008,35 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context) lowlist_item = list_head(sbsref->reflowerindexpr); /* could be NULL */ foreach(uplist_item, sbsref->refupperindexpr) { - appendStringInfoChar(buf, '['); - if (lowlist_item) + Node *upper = (Node *) lfirst(uplist_item); + + if (upper && IsA(upper, FieldAccessorExpr)) { + FieldAccessorExpr *fae = (FieldAccessorExpr *) upper; + + /* Use dot-notation for field access */ + appendStringInfoChar(buf, '.'); + appendStringInfoString(buf, quote_identifier(fae->fieldname)); + + /* Skip matching low index — field access doesn't use slices */ + if (lowlist_item) + lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item); + } + else + { + /* Use JSONB array subscripting */ + appendStringInfoChar(buf, '['); + if (lowlist_item) + { + /* If subexpression is NULL, get_rule_expr prints nothing */ + get_rule_expr((Node *) lfirst(lowlist_item), context, false); + appendStringInfoChar(buf, ':'); + lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item); + } /* If subexpression is NULL, get_rule_expr prints nothing */ - get_rule_expr((Node *) lfirst(lowlist_item), context, false); - appendStringInfoChar(buf, ':'); - lowlist_item = lnext(sbsref->reflowerindexpr, lowlist_item); + get_rule_expr(upper, context, false); + appendStringInfoChar(buf, ']'); } - /* If subexpression is NULL, get_rule_expr prints nothing */ - get_rule_expr((Node *) lfirst(uplist_item), context, false); - appendStringInfoChar(buf, ']'); } } diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 6dfca3cb35b..7e89621bd65 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -708,18 +708,30 @@ typedef struct SubscriptingRef int32 reftypmod pg_node_attr(query_jumble_ignore); /* collation of result, or InvalidOid if none */ Oid refcollid pg_node_attr(query_jumble_ignore); - /* expressions that evaluate to upper container indexes */ + + /* + * expressions that evaluate to upper container indexes or expressions + * that are collected but not evaluated when refjsonbpath is set. + */ List *refupperindexpr; /* - * expressions that evaluate to lower container indexes, or NIL for single - * container element. + * expressions that evaluate to lower container indexes, or NIL for a + * single container element, or expressions that are collected but not + * evaluated when refjsonbpath is set. */ List *reflowerindexpr; /* the expression that evaluates to a container value */ Expr *refexpr; /* expression for the source value, or NULL if fetch */ Expr *refassgnexpr; + + /* + * container-specific extra information, currently used only by jsonb. + * stores a JsonPath expression when jsonb dot notation is used. NULL for + * simple subscripting. + */ + Node *refjsonbpath; } SubscriptingRef; /* @@ -2371,4 +2383,40 @@ typedef struct OnConflictExpr List *exclRelTlist; /* tlist of the EXCLUDED pseudo relation */ } OnConflictExpr; +/* + * FieldAccessorExpr - represents a single object member access using dot-notation + * in JSON simplified accessor syntax (e.g., jsonb_col.a). + * + * These nodes appear as list elements in SubscriptingRef.refupperindexpr to + * indicate JSON object key access. They are not evaluable expressions by + * themselves but serve as placeholders to preserve source-level syntax for + * rule rewriting and deparsing (e.g., in EXPLAIN and view definitions). + * Execution is handled by the enclosing SubscriptingRef. + * + * If dot-notation is used in a SubscriptingRef, the JSON path is represented + * as a flat list of FieldAccessorExpr nodes (for object field access), Const + * nodes (for array indexes), and NULLs (for omitted slice bounds), rather than + * through nested expression trees. + * + * Note: The flat representation avoids nested FieldAccessorExpr chains, + * simplifying evaluation and enabling standard-compliant behavior such as + * conditional array wrapping. This avoids the need for position-aware + * wrapping/unwrapping logic during execution. + * + * For example, in the expression: + * ('{"a": [{"b": 1}]}'::jsonb).a[0].b + * the SubscriptingRef will contain: + * - refexpr: the base expression (the jsonb value) + * - refupperindexpr: [FieldAccessorExpr("a"), Const(0), + * FieldAccessorExpr("b")] + * - reflowerindexpr: [NULL, NULL, NULL] (slice lower bounds not used here) + */ +typedef struct FieldAccessorExpr +{ + NodeTag type; + char *fieldname; /* name of the JSONB object field accessed via + * dot notation */ + Oid faecollid pg_node_attr(query_jumble_ignore); +} FieldAccessorExpr; + #endif /* PRIMNODES_H */ diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c index 39221f9ea5d..e6a7ece6dab 100644 --- a/src/interfaces/ecpg/test/expected/sql-sqljson.c +++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c @@ -417,12 +417,122 @@ if (sqlca.sqlcode < 0) sqlprint();} for (int i = 0; i < sizeof(is_json); i++) printf("Found is_json[%d]: %s\n", i, is_json[i] ? "true" : "false"); - { ECPGdisconnect(__LINE__, "CURRENT"); + { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . \"a\" )", ECPGt_EOIT, + ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), + ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT); #line 118 "sqljson.pgc" if (sqlca.sqlcode < 0) sqlprint();} #line 118 "sqljson.pgc" + printf("Found json=%s\n", json); + + { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb . \"a\" )", ECPGt_EOIT, + ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), + ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT); +#line 121 "sqljson.pgc" + +if (sqlca.sqlcode < 0) sqlprint();} +#line 121 "sqljson.pgc" + + // error + + { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . a )", ECPGt_EOIT, + ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), + ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT); +#line 124 "sqljson.pgc" + +if (sqlca.sqlcode < 0) sqlprint();} +#line 124 "sqljson.pgc" + + printf("Found json=%s\n", json); + + { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . a . b )", ECPGt_EOIT, + ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), + ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT); +#line 127 "sqljson.pgc" + +if (sqlca.sqlcode < 0) sqlprint();} +#line 127 "sqljson.pgc" + + printf("Found json=%s\n", json); + + { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( coalesce ( json ( ( '{\"a\": {\"b\": 1, \"c\": 2}}' :: jsonb ) . c ) , 'null' ) )", ECPGt_EOIT, + ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), + ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT); +#line 130 "sqljson.pgc" + +if (sqlca.sqlcode < 0) sqlprint();} +#line 130 "sqljson.pgc" + + printf("Found json=%s\n", json); + + { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ 0 ] )", ECPGt_EOIT, + ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), + ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT); +#line 133 "sqljson.pgc" + +if (sqlca.sqlcode < 0) sqlprint();} +#line 133 "sqljson.pgc" + + printf("Found json=%s\n", json); + + { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b . x )", ECPGt_EOIT, + ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), + ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT); +#line 136 "sqljson.pgc" + +if (sqlca.sqlcode < 0) sqlprint();} +#line 136 "sqljson.pgc" + + printf("Found json=%s\n", json); + + { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 ] )", ECPGt_EOIT, + ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), + ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT); +#line 139 "sqljson.pgc" + +if (sqlca.sqlcode < 0) sqlprint();} +#line 139 "sqljson.pgc" + + printf("Found json=%s\n", json); + + { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] )", ECPGt_EOIT, + ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), + ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT); +#line 142 "sqljson.pgc" + +if (sqlca.sqlcode < 0) sqlprint();} +#line 142 "sqljson.pgc" + + // error + + { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) . * )", ECPGt_EOIT, + ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), + ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT); +#line 145 "sqljson.pgc" + +if (sqlca.sqlcode < 0) sqlprint();} +#line 145 "sqljson.pgc" + + // error + + { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json ( ( '{\"a\": {\"b\": 1, \"c\": 2}, \"b\": [{\"x\": 1}, {\"x\": [12, {\"y\":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] )", ECPGt_EOIT, + ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char), + ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT); +#line 148 "sqljson.pgc" + +if (sqlca.sqlcode < 0) sqlprint();} +#line 148 "sqljson.pgc" + + // error + + { ECPGdisconnect(__LINE__, "CURRENT"); +#line 151 "sqljson.pgc" + +if (sqlca.sqlcode < 0) sqlprint();} +#line 151 "sqljson.pgc" + return 0; } diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr index e55a95dd711..19f8c58af06 100644 --- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr +++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr @@ -268,5 +268,105 @@ SQL error: cannot use type jsonb in RETURNING clause of JSON_SERIALIZE() on line [NO_PID]: sqlca: code: 0, state: 00000 [NO_PID]: ecpg_get_data on line 102: RESULT: f offset: -1; array: no [NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_execute on line 118: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . "a" ); with 0 parameter(s) on connection ecpg1_regression +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_execute on line 118: using PQexec +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_process_output on line 118: correctly got 1 tuples with 1 fields +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_get_data on line 118: RESULT: {"b": 1, "c": 2} offset: -1; array: no +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_execute on line 121: query: select json ( '{"a": {"b": 1, "c": 2}}' :: jsonb . "a" ); with 0 parameter(s) on connection ecpg1_regression +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_execute on line 121: using PQexec +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_check_PQresult on line 121: bad response - ERROR: schema "jsonb" does not exist +LINE 1: select json ( '{"a": {"b": 1, "c": 2}}' :: jsonb . "a" ) + ^ +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: raising sqlstate 3F000 (sqlcode -400): schema "jsonb" does not exist on line 121 +[NO_PID]: sqlca: code: -400, state: 3F000 +SQL error: schema "jsonb" does not exist on line 121 +[NO_PID]: ecpg_execute on line 124: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . a ); with 0 parameter(s) on connection ecpg1_regression +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_execute on line 124: using PQexec +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_process_output on line 124: correctly got 1 tuples with 1 fields +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_get_data on line 124: RESULT: {"b": 1, "c": 2} offset: -1; array: no +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_execute on line 127: query: select json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . a . b ); with 0 parameter(s) on connection ecpg1_regression +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_execute on line 127: using PQexec +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_process_output on line 127: correctly got 1 tuples with 1 fields +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_get_data on line 127: RESULT: 1 offset: -1; array: no +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_execute on line 130: query: select json ( coalesce ( json ( ( '{"a": {"b": 1, "c": 2}}' :: jsonb ) . c ) , 'null' ) ); with 0 parameter(s) on connection ecpg1_regression +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_execute on line 130: using PQexec +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_process_output on line 130: correctly got 1 tuples with 1 fields +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_get_data on line 130: RESULT: null offset: -1; array: no +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_execute on line 133: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 0 ] ); with 0 parameter(s) on connection ecpg1_regression +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_execute on line 133: using PQexec +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_process_output on line 133: correctly got 1 tuples with 1 fields +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_get_data on line 133: RESULT: {"x": 1} offset: -1; array: no +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_execute on line 136: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b . x ); with 0 parameter(s) on connection ecpg1_regression +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_execute on line 136: using PQexec +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_process_output on line 136: correctly got 1 tuples with 1 fields +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_get_data on line 136: RESULT: [1, [12, {"y": 1}]] offset: -1; array: no +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_execute on line 139: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 ] ); with 0 parameter(s) on connection ecpg1_regression +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_execute on line 139: using PQexec +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_process_output on line 139: correctly got 1 tuples with 1 fields +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_get_data on line 139: RESULT: {"x": 1} offset: -1; array: no +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_execute on line 142: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_execute on line 142: using PQexec +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_check_PQresult on line 142: bad response - ERROR: jsonb subscript does not support slices +LINE 1: ..., {"x": [12, {"y":1}]}]}' :: jsonb ) . b [ 1 ] . x [ 0 : ] ) + ^ +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 142 +[NO_PID]: sqlca: code: -400, state: 42804 +SQL error: jsonb subscript does not support slices on line 142 +[NO_PID]: ecpg_execute on line 145: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) . * ); with 0 parameter(s) on connection ecpg1_regression +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_execute on line 145: using PQexec +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_check_PQresult on line 145: bad response - ERROR: row expansion via "*" is not supported here +LINE 1: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x... + ^ +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: raising sqlstate 0A000 (sqlcode -400): row expansion via "*" is not supported here on line 145 +[NO_PID]: sqlca: code: -400, state: 0A000 +SQL error: row expansion via "*" is not supported here on line 145 +[NO_PID]: ecpg_execute on line 148: query: select json ( ( '{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] ); with 0 parameter(s) on connection ecpg1_regression +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_execute on line 148: using PQexec +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: ecpg_check_PQresult on line 148: bad response - ERROR: jsonb subscript does not support slices +LINE 1: ...x": 1}, {"x": [12, {"y":1}]}]}' :: jsonb ) [ 'b' ] [ 0 : ] ) + ^ +[NO_PID]: sqlca: code: 0, state: 00000 +[NO_PID]: raising sqlstate 42804 (sqlcode -400): jsonb subscript does not support slices on line 148 +[NO_PID]: sqlca: code: -400, state: 42804 +SQL error: jsonb subscript does not support slices on line 148 [NO_PID]: ecpg_finish: connection ecpg1_regression closed [NO_PID]: sqlca: code: 0, state: 00000 diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout index 83f8df13e5a..442d36931f1 100644 --- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout +++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout @@ -28,3 +28,10 @@ Found is_json[4]: false Found is_json[5]: false Found is_json[6]: true Found is_json[7]: false +Found json={"b": 1, "c": 2} +Found json={"b": 1, "c": 2} +Found json=1 +Found json=null +Found json={"x": 1} +Found json=[1, [12, {"y": 1}]] +Found json={"x": 1} diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc index ddcbcc3b3cb..57a9bff424d 100644 --- a/src/interfaces/ecpg/test/sql/sqljson.pgc +++ b/src/interfaces/ecpg/test/sql/sqljson.pgc @@ -115,6 +115,39 @@ EXEC SQL END DECLARE SECTION; for (int i = 0; i < sizeof(is_json); i++) printf("Found is_json[%d]: %s\n", i, is_json[i] ? "true" : "false"); + EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb)."a") INTO :json; + printf("Found json=%s\n", json); + + EXEC SQL SELECT JSON('{"a": {"b": 1, "c": 2}}'::jsonb."a") INTO :json; + // error + + EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).a) INTO :json; + printf("Found json=%s\n", json); + + EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).a.b) INTO :json; + printf("Found json=%s\n", json); + + EXEC SQL SELECT JSON(COALESCE(JSON(('{"a": {"b": 1, "c": 2}}'::jsonb).c), 'null')) INTO :json; + printf("Found json=%s\n", json); + + EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[0]) INTO :json; + printf("Found json=%s\n", json); + + EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b.x) INTO :json; + printf("Found json=%s\n", json); + + EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0]) INTO :json; + printf("Found json=%s\n", json); + + EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).b[1].x[0:]) INTO :json; + // error + + EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb).*) INTO :json; + // error + + EXEC SQL SELECT JSON(('{"a": {"b": 1, "c": 2}, "b": [{"x": 1}, {"x": [12, {"y":1}]}]}'::jsonb)['b'][0:]) INTO :json; + // error + EXEC SQL DISCONNECT; return 0; diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out index 5a1eb18aba2..d781f1518eb 100644 --- a/src/test/regress/expected/jsonb.out +++ b/src/test/regress/expected/jsonb.out @@ -4989,6 +4989,12 @@ select ('123'::jsonb)['a']; (1 row) +select ('123'::jsonb).a; + a +--- + +(1 row) + select ('123'::jsonb)[0]; jsonb ------- @@ -5001,12 +5007,24 @@ select ('123'::jsonb)[NULL]; (1 row) +select ('123'::jsonb).NULL; + null +------ + +(1 row) + select ('{"a": 1}'::jsonb)['a']; jsonb ------- 1 (1 row) +select ('{"a": 1}'::jsonb).a; + a +--- + 1 +(1 row) + select ('{"a": 1}'::jsonb)[0]; jsonb ------- @@ -5019,6 +5037,12 @@ select ('{"a": 1}'::jsonb)['not_exist']; (1 row) +select ('{"a": 1}'::jsonb)."not_exist"; + not_exist +----------- + +(1 row) + select ('{"a": 1}'::jsonb)[NULL]; jsonb ------- @@ -5031,6 +5055,12 @@ select ('[1, "2", null]'::jsonb)['a']; (1 row) +select ('[1, "2", null]'::jsonb).a; + a +--- + +(1 row) + select ('[1, "2", null]'::jsonb)[0]; jsonb ------- @@ -5043,6 +5073,12 @@ select ('[1, "2", null]'::jsonb)['1']; "2" (1 row) +select ('[1, "2", null]'::jsonb)."1"; + 1 +--- + +(1 row) + select ('[1, "2", null]'::jsonb)[1.0]; ERROR: subscript type numeric is not supported LINE 1: select ('[1, "2", null]'::jsonb)[1.0]; @@ -5072,6 +5108,12 @@ select ('[1, "2", null]'::jsonb)[1]['a']; (1 row) +select ('[1, "2", null]'::jsonb)[1].a; + a +--- + +(1 row) + select ('[1, "2", null]'::jsonb)[1][0]; jsonb ------- @@ -5084,56 +5126,127 @@ select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b']; "c" (1 row) +select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b; + b +----- + "c" +(1 row) + select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']; jsonb ----------- [1, 2, 3] (1 row) +select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d; + d +----------- + [1, 2, 3] +(1 row) + select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1]; jsonb ------- 2 (1 row) +select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1]; + d +--- + 2 +(1 row) + select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a']; jsonb ------- (1 row) +select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a']; + d +--- + +(1 row) + +select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a; + a +--- + +(1 row) + +select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d['a'][0]; + d +--- + 1 +(1 row) + +select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d.a[0]; + a +--- + 1 +(1 row) + select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']; jsonb --------------- {"a2": "aaa"} (1 row) +select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1; + a1 +--------------- + {"a2": "aaa"} +(1 row) + select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']; jsonb ------- "aaa" (1 row) +select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2; + a2 +------- + "aaa" +(1 row) + select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3']; jsonb ------- (1 row) +select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3; + a3 +---- + +(1 row) + select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1']; jsonb ----------------------- ["aaa", "bbb", "ccc"] (1 row) +select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1; + b1 +----------------------- + ["aaa", "bbb", "ccc"] +(1 row) + select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2]; jsonb ------- "ccc" (1 row) --- slices are not supported -select ('{"a": 1}'::jsonb)['a':'b']; +select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2]; + b1 +------- + "ccc" +(1 row) + +select ('{"a": 1}'::jsonb)['a':'b']; -- fails ERROR: jsonb subscript does not support slices LINE 1: select ('{"a": 1}'::jsonb)['a':'b']; ^ @@ -5831,3 +5944,287 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8; 12345 (1 row) +-- dot notation +CREATE TABLE test_jsonb_dot_notation AS +SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb; +SELECT (jb).a FROM test_jsonb_dot_notation; + a +------------------------------------------------------------------------- + [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}] +(1 row) + +SELECT (jb)."a" FROM test_jsonb_dot_notation; -- double quote should work + a +------------------------------------------------------------------------- + [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}] +(1 row) + +SELECT (jb).'a' FROM test_jsonb_dot_notation; -- single quote should not work +ERROR: syntax error at or near "'a'" +LINE 1: SELECT (jb).'a' FROM test_jsonb_dot_notation; + ^ +SELECT (jb).b FROM test_jsonb_dot_notation; + b +--------------------------------------------------- + [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}] +(1 row) + +SELECT (jb).c FROM test_jsonb_dot_notation; + c +--- + +(1 row) + +SELECT (jb).a.b FROM test_jsonb_dot_notation; + b +------------ + ["c", "d"] +(1 row) + +SELECT (jb).a[2].b FROM test_jsonb_dot_notation; + b +----- + "c" +(1 row) + +SELECT (jb).a.x.y FROM test_jsonb_dot_notation; + y +------- + "yyy" +(1 row) + +SELECT (jb).b.x.z FROM test_jsonb_dot_notation; + z +------- + "ZZZ" +(1 row) + +SELECT (jb).a.b.c FROM test_jsonb_dot_notation; + c +--- + +(1 row) + +SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails +ERROR: jsonb subscript does not support slices +SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails +ERROR: type jsonb is not composite +-- explains should work +EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t; + QUERY PLAN +---------------------------------------------- + Seq Scan on public.test_jsonb_dot_notation t + Output: (jb).a +(2 rows) + +SELECT (t.jb).a FROM test_jsonb_dot_notation t; + a +------------------------------------------------------------------------- + [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}] +(1 row) + +EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation; + QUERY PLAN +-------------------------------------------- + Seq Scan on public.test_jsonb_dot_notation + Output: (jb).a[1] +(2 rows) + +SELECT (jb).a[1] FROM test_jsonb_dot_notation; + a +--- + 2 +(1 row) + +-- views should work +CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation; +\sv test_jsonb_dot_notation_v1 +CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS + SELECT (jb).a[3].x.y AS y + FROM test_jsonb_dot_notation +-- mixed syntax +DROP VIEW test_jsonb_dot_notation_v1; +EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation; + QUERY PLAN +-------------------------------------------- + Seq Scan on public.test_jsonb_dot_notation + Output: (jb['a'::text]).b +(2 rows) + +SELECT (jb)['a'].b FROM test_jsonb_dot_notation; -- returns an array due to lax mode + b +------------ + ["c", "d"] +(1 row) + +CREATE VIEW public.test_jsonb_dot_notation_v1 AS +SELECT (jb)['a'].b FROM test_jsonb_dot_notation; +\sv test_jsonb_dot_notation_v1 +CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS + SELECT (jb['a'::text]).b AS b + FROM test_jsonb_dot_notation +SELECT * from test_jsonb_dot_notation_v1; + b +------------ + ["c", "d"] +(1 row) + +DROP VIEW public.test_jsonb_dot_notation_v1; +EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a['b'] FROM test_jsonb_dot_notation; + QUERY PLAN +-------------------------------------------- + Seq Scan on public.test_jsonb_dot_notation + Output: ((jb).a)['b'::text] +(2 rows) + +SELECT (jb).a['b'] FROM test_jsonb_dot_notation; -- returns NULL due to strict mode with warnings + a +--- + +(1 row) + +CREATE VIEW public.test_jsonb_dot_notation_v1 AS +SELECT (jb).a['b'] FROM test_jsonb_dot_notation; +\sv test_jsonb_dot_notation_v1 +CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS + SELECT ((jb).a)['b'::text] AS a + FROM test_jsonb_dot_notation +SELECT * from test_jsonb_dot_notation_v1; + a +--- + +(1 row) + +DROP VIEW public.test_jsonb_dot_notation_v1; +EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation; + QUERY PLAN +-------------------------------------------- + Seq Scan on public.test_jsonb_dot_notation + Output: ((jb).b.x)['z'::text] +(2 rows) + +SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation; -- warnings + x +------- + "ZZZ" +(1 row) + +CREATE VIEW public.test_jsonb_dot_notation_v1 AS +SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation; +\sv test_jsonb_dot_notation_v1 +CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS + SELECT ((jb).b.x)['z'::text] AS x + FROM test_jsonb_dot_notation +SELECT * from test_jsonb_dot_notation_v1; + x +------- + "ZZZ" +(1 row) + +DROP VIEW public.test_jsonb_dot_notation_v1; +EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation; + QUERY PLAN +-------------------------------------------- + Seq Scan on public.test_jsonb_dot_notation + Output: ((jb['b'::text]).x)['z'::text] +(2 rows) + +SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation; -- warnings + x +------- + "ZZZ" +(1 row) + +CREATE VIEW public.test_jsonb_dot_notation_v1 AS +SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation; +\sv test_jsonb_dot_notation_v1 +CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS + SELECT ((jb['b'::text]).x)['z'::text] AS x + FROM test_jsonb_dot_notation +SELECT * from test_jsonb_dot_notation_v1; + x +------- + "ZZZ" +(1 row) + +DROP VIEW public.test_jsonb_dot_notation_v1; +EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x'].z FROM test_jsonb_dot_notation; + QUERY PLAN +-------------------------------------------- + Seq Scan on public.test_jsonb_dot_notation + Output: (((jb).b)['x'::text]).z +(2 rows) + +SELECT (jb).b['x'].z FROM test_jsonb_dot_notation; -- returns NULL with warnings + z +--- + +(1 row) + +CREATE VIEW public.test_jsonb_dot_notation_v1 AS +SELECT (jb).b['x'].z FROM test_jsonb_dot_notation; +\sv test_jsonb_dot_notation_v1 +CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS + SELECT (((jb).b)['x'::text]).z AS z + FROM test_jsonb_dot_notation +SELECT * from test_jsonb_dot_notation_v1; + z +--- + +(1 row) + +DROP VIEW public.test_jsonb_dot_notation_v1; +EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation; + QUERY PLAN +-------------------------------------------- + Seq Scan on public.test_jsonb_dot_notation + Output: ((jb).b)['x'::text]['z'::text] +(2 rows) + +SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation; -- returns NULL with warnings + b +--- + +(1 row) + +CREATE VIEW public.test_jsonb_dot_notation_v1 AS +SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation; +\sv test_jsonb_dot_notation_v1 +CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS + SELECT ((jb).b)['x'::text]['z'::text] AS b + FROM test_jsonb_dot_notation +SELECT * from test_jsonb_dot_notation_v1; + b +--- + +(1 row) + +DROP VIEW public.test_jsonb_dot_notation_v1; +EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation; + QUERY PLAN +-------------------------------------------- + Seq Scan on public.test_jsonb_dot_notation + Output: (jb['b'::text]['x'::text]).z +(2 rows) + +SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation; -- returns NULL with warnings + z +--- + +(1 row) + +CREATE VIEW public.test_jsonb_dot_notation_v1 AS +SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation; +\sv test_jsonb_dot_notation_v1 +CREATE OR REPLACE VIEW public.test_jsonb_dot_notation_v1 AS + SELECT (jb['b'::text]['x'::text]).z AS z + FROM test_jsonb_dot_notation +SELECT * from test_jsonb_dot_notation_v1; + z +--- + +(1 row) + +DROP VIEW public.test_jsonb_dot_notation_v1; +-- clean up +DROP TABLE test_jsonb_dot_notation; diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql index 57c11acddfe..a0fad90a9d5 100644 --- a/src/test/regress/sql/jsonb.sql +++ b/src/test/regress/sql/jsonb.sql @@ -1304,33 +1304,51 @@ select jsonb_insert('{"a": {"b": "value"}}', '{a, b}', '"new_value"', true); -- jsonb subscript select ('123'::jsonb)['a']; +select ('123'::jsonb).a; select ('123'::jsonb)[0]; select ('123'::jsonb)[NULL]; +select ('123'::jsonb).NULL; select ('{"a": 1}'::jsonb)['a']; +select ('{"a": 1}'::jsonb).a; select ('{"a": 1}'::jsonb)[0]; select ('{"a": 1}'::jsonb)['not_exist']; +select ('{"a": 1}'::jsonb)."not_exist"; select ('{"a": 1}'::jsonb)[NULL]; select ('[1, "2", null]'::jsonb)['a']; +select ('[1, "2", null]'::jsonb).a; select ('[1, "2", null]'::jsonb)[0]; select ('[1, "2", null]'::jsonb)['1']; +select ('[1, "2", null]'::jsonb)."1"; select ('[1, "2", null]'::jsonb)[1.0]; select ('[1, "2", null]'::jsonb)[2]; select ('[1, "2", null]'::jsonb)[3]; select ('[1, "2", null]'::jsonb)[-2]; select ('[1, "2", null]'::jsonb)[1]['a']; +select ('[1, "2", null]'::jsonb)[1].a; select ('[1, "2", null]'::jsonb)[1][0]; select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['b']; +select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).b; select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']; +select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d; select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d'][1]; +select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d[1]; select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb)['d']['a']; +select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d['a']; +select ('{"a": 1, "b": "c", "d": [1, 2, 3]}'::jsonb).d.a; +select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d['a'][0]; +select ('{"a": 1, "b": "c", "d": {"a": [1, 2, 3]}}'::jsonb).d.a[0]; select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']; +select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1; select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']; +select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2; select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb)['a']['a1']['a2']['a3']; +select ('{"a": {"a1": {"a2": "aaa"}}, "b": "bbb", "c": "ccc"}'::jsonb).a.a1.a2.a3; select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1']; +select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1; select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb)['a'][1]['b1'][2]; +select ('{"a": ["a1", {"b1": ["aaa", "bbb", "ccc"]}], "b": "bb"}'::jsonb).a[1].b1[2]; --- slices are not supported -select ('{"a": 1}'::jsonb)['a':'b']; +select ('{"a": 1}'::jsonb)['a':'b']; -- fails select ('[1, "2", null]'::jsonb)[1:2]; select ('[1, "2", null]'::jsonb)[:2]; select ('[1, "2", null]'::jsonb)[1:]; @@ -1590,3 +1608,92 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::float8; select '12345.0000000000000000000000000000000000000000000005'::jsonb::int2; select '12345.0000000000000000000000000000000000000000000005'::jsonb::int4; select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8; + +-- dot notation +CREATE TABLE test_jsonb_dot_notation AS +SELECT '{"a": [1, 2, {"b": "c"}, {"b": "d", "e": "f", "x": {"y": "yyy", "z": "zzz"}}], "b": [3, 4, {"b": "g", "x": {"y": "YYY", "z": "ZZZ"}}]}'::jsonb jb; + +SELECT (jb).a FROM test_jsonb_dot_notation; +SELECT (jb)."a" FROM test_jsonb_dot_notation; -- double quote should work +SELECT (jb).'a' FROM test_jsonb_dot_notation; -- single quote should not work +SELECT (jb).b FROM test_jsonb_dot_notation; +SELECT (jb).c FROM test_jsonb_dot_notation; +SELECT (jb).a.b FROM test_jsonb_dot_notation; +SELECT (jb).a[2].b FROM test_jsonb_dot_notation; +SELECT (jb).a.x.y FROM test_jsonb_dot_notation; +SELECT (jb).b.x.z FROM test_jsonb_dot_notation; +SELECT (jb).a.b.c FROM test_jsonb_dot_notation; +SELECT ((jb).b)[:].x FROM test_jsonb_dot_notation t; -- fails +SELECT (jb).a.* FROM test_jsonb_dot_notation; -- fails + +-- explains should work +EXPLAIN (VERBOSE, COSTS OFF) SELECT (t.jb).a FROM test_jsonb_dot_notation t; +SELECT (t.jb).a FROM test_jsonb_dot_notation t; +EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a[1] FROM test_jsonb_dot_notation; +SELECT (jb).a[1] FROM test_jsonb_dot_notation; + +-- views should work +CREATE VIEW test_jsonb_dot_notation_v1 AS SELECT (jb).a[3].x.y FROM test_jsonb_dot_notation; +\sv test_jsonb_dot_notation_v1 + +-- mixed syntax +DROP VIEW test_jsonb_dot_notation_v1; + +EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['a'].b FROM test_jsonb_dot_notation; +SELECT (jb)['a'].b FROM test_jsonb_dot_notation; -- returns an array due to lax mode +CREATE VIEW public.test_jsonb_dot_notation_v1 AS +SELECT (jb)['a'].b FROM test_jsonb_dot_notation; +\sv test_jsonb_dot_notation_v1 +SELECT * from test_jsonb_dot_notation_v1; +DROP VIEW public.test_jsonb_dot_notation_v1; + +EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).a['b'] FROM test_jsonb_dot_notation; +SELECT (jb).a['b'] FROM test_jsonb_dot_notation; -- returns NULL due to strict mode with warnings +CREATE VIEW public.test_jsonb_dot_notation_v1 AS +SELECT (jb).a['b'] FROM test_jsonb_dot_notation; +\sv test_jsonb_dot_notation_v1 +SELECT * from test_jsonb_dot_notation_v1; +DROP VIEW public.test_jsonb_dot_notation_v1; + +EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation; +SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation; -- warnings +CREATE VIEW public.test_jsonb_dot_notation_v1 AS +SELECT (jb).b.x['z'] FROM test_jsonb_dot_notation; +\sv test_jsonb_dot_notation_v1 +SELECT * from test_jsonb_dot_notation_v1; +DROP VIEW public.test_jsonb_dot_notation_v1; + +EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation; +SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation; -- warnings +CREATE VIEW public.test_jsonb_dot_notation_v1 AS +SELECT (jb)['b'].x['z'] FROM test_jsonb_dot_notation; +\sv test_jsonb_dot_notation_v1 +SELECT * from test_jsonb_dot_notation_v1; +DROP VIEW public.test_jsonb_dot_notation_v1; + +EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x'].z FROM test_jsonb_dot_notation; +SELECT (jb).b['x'].z FROM test_jsonb_dot_notation; -- returns NULL with warnings +CREATE VIEW public.test_jsonb_dot_notation_v1 AS +SELECT (jb).b['x'].z FROM test_jsonb_dot_notation; +\sv test_jsonb_dot_notation_v1 +SELECT * from test_jsonb_dot_notation_v1; +DROP VIEW public.test_jsonb_dot_notation_v1; + +EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation; +SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation; -- returns NULL with warnings +CREATE VIEW public.test_jsonb_dot_notation_v1 AS +SELECT (jb).b['x']['z'] FROM test_jsonb_dot_notation; +\sv test_jsonb_dot_notation_v1 +SELECT * from test_jsonb_dot_notation_v1; +DROP VIEW public.test_jsonb_dot_notation_v1; + +EXPLAIN (VERBOSE, COSTS OFF) SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation; +SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation; -- returns NULL with warnings +CREATE VIEW public.test_jsonb_dot_notation_v1 AS +SELECT (jb)['b']['x'].z FROM test_jsonb_dot_notation; +\sv test_jsonb_dot_notation_v1 +SELECT * from test_jsonb_dot_notation_v1; +DROP VIEW public.test_jsonb_dot_notation_v1; + +-- clean up +DROP TABLE test_jsonb_dot_notation; \ No newline at end of file diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index e4a9ec65ab4..6fca8eb7b23 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -805,6 +805,7 @@ FdwRoutine FetchDirection FetchDirectionKeywords FetchStmt +FieldAccessorExpr FieldSelect FieldStore File -- 2.39.5 (Apple Git-154)