public inbox for [email protected]
help / color / mirror / Atom feedRe: SQL/JSON revisited
4+ messages / 3 participants
[nested] [flat]
* Re: SQL/JSON revisited
@ 2023-03-31 16:49 Alvaro Herrera <[email protected]>
0 siblings, 0 replies; 4+ messages in thread
From: Alvaro Herrera @ 2023-03-31 16:49 UTC (permalink / raw)
To: Alexander Lakhin <[email protected]>; +Cc: Erik Rijkers <[email protected]>; Andrew Dunstan <[email protected]>; Amit Langote <[email protected]>; Peter Eisentraut <[email protected]>; Andres Freund <[email protected]>; vignesh C <[email protected]>; pgsql-hackers; [email protected]
Here's v14 of the IS JSON predicate. I find no further problems here
and intend to get it pushed later today.
--
Álvaro Herrera PostgreSQL Developer — https://www.EnterpriseDB.com/
"Siempre hay que alimentar a los dioses, aunque la tierra esté seca" (Orual)
Attachments:
[text/x-diff] v14-0001-SQL-JSON-support-the-IS-JSON-predicate.patch (64.8K, ../../[email protected]/2-v14-0001-SQL-JSON-support-the-IS-JSON-predicate.patch)
download | inline diff:
From 998ec6bd170e83cdb916ab40bec5cac56ecd1d81 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <[email protected]>
Date: Fri, 31 Mar 2023 18:47:51 +0200
Subject: [PATCH v14] SQL/JSON: support the IS JSON predicate
This patch introduces the SQL standard IS JSON predicate. It operates
on text and bytea values representing JSON, as well as on the json and
jsonb types. Each test has IS and IS NOT variants and supports a WITH
UNIQUE KEYS flag. The tests are:
IS JSON [VALUE]
IS JSON ARRAY
IS JSON OBJECT
IS JSON SCALAR
These should be self-explanatory.
The WITH UNIQUE KEYS flag makes these return false when duplicate keys
exist in any object within the value, not necessarily directly contained
in the outermost object.
Author: Nikita Glukhov <[email protected]>
Author: Teodor Sigaev <[email protected]>
Author: Oleg Bartunov <[email protected]>
Author: Alexander Korotkov <[email protected]>
Author: Amit Langote <[email protected]>
Author: Andrew Dunstan <[email protected]>
Reviewers have included (in no particular order) Andres Freund, Alexander
Korotkov, Pavel Stehule, Andrew Alsup, Erik Rijkers, Zihong Yu,
Himanshu Upadhyaya, Daniel Gustafsson, Justin Pryzby.
Discussion: https://postgr.es/m/CAF4Au4w2x-5LTnN_bxky-mq4=WOqsGsxSpENCzHRAzSnEd8+WQ@mail.gmail.com
Discussion: https://postgr.es/m/[email protected]
Discussion: https://postgr.es/m/[email protected]
Discussion: https://postgr.es/m/abd9b83b-aa66-f230-3d6d-734817f0995d%40postgresql.org
---
doc/src/sgml/func.sgml | 80 +++++++
src/backend/executor/execExpr.c | 13 ++
src/backend/executor/execExprInterp.c | 99 +++++++++
src/backend/jit/llvm/llvmjit_expr.c | 6 +
src/backend/jit/llvm/llvmjit_types.c | 1 +
src/backend/nodes/makefuncs.c | 19 ++
src/backend/nodes/nodeFuncs.c | 26 +++
src/backend/parser/gram.y | 70 ++++++-
src/backend/parser/parse_expr.c | 76 +++++++
src/backend/parser/parser.c | 3 +
src/backend/utils/adt/json.c | 132 +++++++++++-
src/backend/utils/adt/jsonfuncs.c | 20 ++
src/backend/utils/adt/ruleutils.c | 37 ++++
src/include/catalog/catversion.h | 2 +-
src/include/executor/execExpr.h | 8 +
src/include/nodes/makefuncs.h | 3 +
src/include/nodes/primnodes.h | 26 +++
src/include/parser/kwlist.h | 1 +
src/include/utils/json.h | 1 +
src/include/utils/jsonfuncs.h | 3 +
src/interfaces/ecpg/preproc/parse.pl | 1 +
src/interfaces/ecpg/preproc/parser.c | 3 +
.../ecpg/test/expected/sql-sqljson.c | 73 +++++--
.../ecpg/test/expected/sql-sqljson.stderr | 86 +++++---
.../ecpg/test/expected/sql-sqljson.stdout | 8 +
src/interfaces/ecpg/test/sql/sqljson.pgc | 17 ++
src/test/regress/expected/sqljson.out | 198 ++++++++++++++++++
src/test/regress/sql/sqljson.sql | 96 +++++++++
28 files changed, 1039 insertions(+), 69 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 38e7f46760..918a492234 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -16005,6 +16005,86 @@ table2-mapping
</tgroup>
</table>
+ <para>
+ <xref linkend="functions-sqljson-misc" /> details SQL/JSON
+ facilities for testing JSON.
+ </para>
+
+ <table id="functions-sqljson-misc">
+ <title>SQL/JSON Testing Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function signature
+ </para>
+ <para>
+ Description
+ </para>
+ <para>
+ Example(s)
+ </para></entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm><primary>IS JSON</primary></indexterm>
+ <replaceable>expression</replaceable> <literal>IS</literal> <optional> <literal>NOT</literal> </optional> <literal>JSON</literal>
+ <optional> { <literal>VALUE</literal> | <literal>SCALAR</literal> | <literal>ARRAY</literal> | <literal>OBJECT</literal> } </optional>
+ <optional> { <literal>WITH</literal> | <literal>WITHOUT</literal> } <literal>UNIQUE</literal> <optional> <literal>KEYS</literal> </optional> </optional>
+ </para>
+ <para>
+ This predicate tests whether <replaceable>expression</replaceable> can be
+ parsed as JSON, possibly of a specified type.
+ If <literal>SCALAR</literal> or <literal>ARRAY</literal> or
+ <literal>OBJECT</literal> is specified, the
+ test is whether or not the JSON is of that particular type. If
+ <literal>WITH UNIQUE KEYS</literal> is specified, then any object in the
+ <replaceable>expression</replaceable> is also tested to see if it
+ has duplicate keys.
+ </para>
+ <para>
+<programlisting>
+SELECT js,
+ js IS JSON "json?",
+ js IS JSON SCALAR "scalar?",
+ js IS JSON OBJECT "object?",
+ js IS JSON ARRAY "array?"
+FROM (VALUES
+ ('123'), ('"abc"'), ('{"a": "b"}'), ('[1,2]'),('abc')) foo(js);
+ js | json? | scalar? | object? | array?
+------------+-------+---------+---------+--------
+ 123 | t | t | f | f
+ "abc" | t | t | f | f
+ {"a": "b"} | t | f | t | f
+ [1,2] | t | f | f | t
+ abc | f | f | f | f
+</programlisting>
+ </para>
+ <para>
+<programlisting>
+SELECT js,
+ js IS JSON OBJECT "object?",
+ js IS JSON ARRAY "array?",
+ js IS JSON ARRAY WITH UNIQUE KEYS "array w. UK?",
+ js IS JSON ARRAY WITHOUT UNIQUE KEYS "array w/o UK?"
+FROM (VALUES ('[{"a":"1"},
+ {"b":"2","b":"3"}]')) foo(js);
+-[ RECORD 1 ]-+--------------------
+js | [{"a":"1"}, +
+ | {"b":"2","b":"3"}]
+object? | f
+array? | t
+array w. UK? | f
+array w/o UK? | t
+</programlisting>
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
<para>
<xref linkend="functions-json-processing-table"/> shows the functions that
are available for processing <type>json</type> and <type>jsonb</type> values.
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 6c5a378029..4c6700de04 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -2370,6 +2370,19 @@ ExecInitExprRec(Expr *node, ExprState *state,
}
break;
+ case T_JsonIsPredicate:
+ {
+ JsonIsPredicate *pred = (JsonIsPredicate *) node;
+
+ ExecInitExprRec((Expr *) pred->expr, state, resv, resnull);
+
+ scratch.opcode = EEOP_IS_JSON;
+ scratch.d.is_json.pred = pred;
+
+ ExprEvalPushStep(state, &scratch);
+ break;
+ }
+
case T_NullTest:
{
NullTest *ntest = (NullTest *) node;
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 5e55592f80..4cd46f1717 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -73,6 +73,7 @@
#include "utils/expandedrecord.h"
#include "utils/json.h"
#include "utils/jsonb.h"
+#include "utils/jsonfuncs.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/timestamp.h"
@@ -477,6 +478,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
&&CASE_EEOP_HASHED_SCALARARRAYOP,
&&CASE_EEOP_XMLEXPR,
&&CASE_EEOP_JSON_CONSTRUCTOR,
+ &&CASE_EEOP_IS_JSON,
&&CASE_EEOP_AGGREF,
&&CASE_EEOP_GROUPING_FUNC,
&&CASE_EEOP_WINDOW_FUNC,
@@ -1521,6 +1523,14 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_NEXT();
}
+ EEO_CASE(EEOP_IS_JSON)
+ {
+ /* too complex for an inline implementation */
+ ExecEvalJsonIsPredicate(state, op);
+
+ EEO_NEXT();
+ }
+
EEO_CASE(EEOP_AGGREF)
{
/*
@@ -3921,6 +3931,95 @@ ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
*op->resnull = isnull;
}
+/*
+ * Evaluate a IS JSON predicate.
+ */
+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
*
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index 12d39394b3..daefe66f40 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -1848,6 +1848,12 @@ llvm_compile_expr(ExprState *state)
LLVMBuildBr(b, opblocks[opno + 1]);
break;
+ case EEOP_IS_JSON:
+ build_EvalXFunc(b, mod, "ExecEvalJsonIsPredicate",
+ v_state, op);
+ LLVMBuildBr(b, opblocks[opno + 1]);
+ break;
+
case EEOP_AGGREF:
{
LLVMValueRef v_aggno;
diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c
index 315eeb1172..f61d9390ee 100644
--- a/src/backend/jit/llvm/llvmjit_types.c
+++ b/src/backend/jit/llvm/llvmjit_types.c
@@ -133,6 +133,7 @@ void *referenced_functions[] =
ExecEvalWholeRowVar,
ExecEvalXmlExpr,
ExecEvalJsonConstructor,
+ ExecEvalJsonIsPredicate,
MakeExpandedObjectReadOnlyInternal,
slot_getmissingattrs,
slot_getsomeattrs_int,
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 23c7152806..39e1884cf4 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -894,3 +894,22 @@ makeJsonKeyValue(Node *key, Node *value)
return (Node *) n;
}
+
+/*
+ * makeJsonIsPredicate -
+ * creates a JsonIsPredicate node
+ */
+Node *
+makeJsonIsPredicate(Node *expr, JsonFormat *format, JsonValueType item_type,
+ bool unique_keys, int location)
+{
+ JsonIsPredicate *n = makeNode(JsonIsPredicate);
+
+ n->expr = expr;
+ n->format = format;
+ n->item_type = item_type;
+ n->unique_keys = unique_keys;
+ n->location = location;
+
+ return (Node *) n;
+}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 69907fbcde..fdb0c6b3fe 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -261,6 +261,9 @@ exprType(const Node *expr)
case T_JsonConstructorExpr:
type = ((const JsonConstructorExpr *) expr)->returning->typid;
break;
+ case T_JsonIsPredicate:
+ type = BOOLOID;
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
type = InvalidOid; /* keep compiler quiet */
@@ -983,6 +986,9 @@ exprCollation(const Node *expr)
coll = InvalidOid;
}
break;
+ case T_JsonIsPredicate:
+ coll = InvalidOid; /* result is always an boolean type */
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
coll = InvalidOid; /* keep compiler quiet */
@@ -1205,6 +1211,9 @@ exprSetCollation(Node *expr, Oid collation)
* json[b] type */
}
break;
+ case T_JsonIsPredicate:
+ Assert(!OidIsValid(collation)); /* result is always boolean */
+ break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
break;
@@ -1653,6 +1662,9 @@ exprLocation(const Node *expr)
case T_JsonConstructorExpr:
loc = ((const JsonConstructorExpr *) expr)->location;
break;
+ case T_JsonIsPredicate:
+ loc = ((const JsonIsPredicate *) expr)->location;
+ break;
default:
/* for any other node type it's just unknown... */
loc = -1;
@@ -2406,6 +2418,8 @@ expression_tree_walker_impl(Node *node,
return true;
}
break;
+ case T_JsonIsPredicate:
+ return walker(((JsonIsPredicate *) node)->expr, context);
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
@@ -3413,6 +3427,16 @@ expression_tree_mutator_impl(Node *node,
MUTATE(newnode->coercion, jve->coercion, Expr *);
MUTATE(newnode->returning, jve->returning, JsonReturning *);
+ return (Node *) newnode;
+ }
+ case T_JsonIsPredicate:
+ {
+ JsonIsPredicate *pred = (JsonIsPredicate *) node;
+ JsonIsPredicate *newnode;
+
+ FLATCOPY(newnode, pred, JsonIsPredicate);
+ MUTATE(newnode->expr, pred->expr, Node *);
+
return (Node *) newnode;
}
default:
@@ -4261,6 +4285,8 @@ raw_expression_tree_walker_impl(Node *node,
return true;
}
break;
+ case T_JsonIsPredicate:
+ return walker(((JsonIsPredicate *) node)->expr, context);
default:
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(node));
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index f3ddc34afb..a04c02f58f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -655,6 +655,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
json_array_aggregate_order_by_clause_opt
%type <ival> json_encoding_clause_opt
+ json_predicate_type_constraint
%type <boolean> json_key_uniqueness_constraint_opt
json_object_constructor_null_clause_opt
@@ -754,7 +755,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
RESET RESTART RESTRICT RETURN RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
ROUTINE ROUTINES ROW ROWS RULE
- SAVEPOINT SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
+ SAVEPOINT SCALAR SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT
+ SEQUENCE SEQUENCES
SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
START STATEMENT STATISTICS STDIN STDOUT STORAGE STORED STRICT_P STRIP_P
@@ -791,7 +793,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
* FORMAT_LA, NULLS_LA, WITH_LA, and WITHOUT_LA are needed to make the grammar
* LALR(1).
*/
-%token FORMAT_LA NOT_LA NULLS_LA WITH_LA WITHOUT_LA
+%token FORMAT_LA NOT_LA NULLS_LA WITH_LA WITH_UNIQUE_LA WITHOUT_LA
/*
* The grammar likewise thinks these tokens are keywords, but they are never
@@ -818,6 +820,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%nonassoc '<' '>' '=' LESS_EQUALS GREATER_EQUALS NOT_EQUALS
%nonassoc BETWEEN IN_P LIKE ILIKE SIMILAR NOT_LA
%nonassoc ESCAPE /* ESCAPE must be just above LIKE/ILIKE/SIMILAR */
+
+/* SQL/JSON related keywords */
+%nonassoc UNIQUE JSON
+%nonassoc KEYS OBJECT_P SCALAR VALUE_P
+%nonassoc WITHOUT_LA WITH_UNIQUE_LA
+
/*
* To support target_el without AS, it used to be necessary to assign IDENT an
* explicit precedence just less than Op. While that's not really necessary
@@ -14850,6 +14858,44 @@ a_expr: c_expr { $$ = $1; }
@2),
@2);
}
+ | a_expr IS json_predicate_type_constraint
+ json_key_uniqueness_constraint_opt %prec IS
+ {
+ JsonFormat *format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1);
+
+ $$ = makeJsonIsPredicate($1, format, $3, $4, @1);
+ }
+ /*
+ * Required by SQL/JSON, but there are conflicts
+ | a_expr
+ FORMAT_LA JSON json_encoding_clause_opt
+ IS json_predicate_type_constraint
+ json_key_uniqueness_constraint_opt %prec IS
+ {
+ $3.location = @2;
+ $$ = makeJsonIsPredicate($1, $3, $5, $6, @1);
+ }
+ */
+ | a_expr IS NOT
+ json_predicate_type_constraint
+ json_key_uniqueness_constraint_opt %prec IS
+ {
+ JsonFormat *format = makeJsonFormat(JS_FORMAT_DEFAULT, JS_ENC_DEFAULT, -1);
+
+ $$ = makeNotExpr(makeJsonIsPredicate($1, format, $4, $5, @1), @1);
+ }
+ /*
+ * Required by SQL/JSON, but there are conflicts
+ | a_expr
+ FORMAT_LA JSON json_encoding_clause_opt
+ IS NOT
+ json_predicate_type_constraint
+ json_key_uniqueness_constraint_opt %prec IS
+ {
+ $3.location = @2;
+ $$ = makeNotExpr(makeJsonIsPredicate($1, $3, $6, $7, @1), @1);
+ }
+ */
| DEFAULT
{
/*
@@ -16406,13 +16452,21 @@ json_output_clause_opt:
| /* EMPTY */ { $$ = NULL; }
;
+json_predicate_type_constraint:
+ JSON { $$ = JS_TYPE_ANY; }
+ | JSON VALUE_P { $$ = JS_TYPE_ANY; }
+ | JSON ARRAY { $$ = JS_TYPE_ARRAY; }
+ | JSON OBJECT_P { $$ = JS_TYPE_OBJECT; }
+ | JSON SCALAR { $$ = JS_TYPE_SCALAR; }
+ ;
+
/* KEYS is a noise word here */
json_key_uniqueness_constraint_opt:
- WITH UNIQUE KEYS { $$ = true; }
- | WITH UNIQUE { $$ = true; }
- | WITHOUT_LA UNIQUE KEYS { $$ = false; }
- | WITHOUT_LA UNIQUE { $$ = false; }
- | /* EMPTY */ { $$ = false; }
+ WITH_UNIQUE_LA UNIQUE KEYS { $$ = true; }
+ | WITH_UNIQUE_LA UNIQUE { $$ = true; }
+ | WITHOUT_LA UNIQUE KEYS { $$ = false; }
+ | WITHOUT_LA UNIQUE { $$ = false; }
+ | /* EMPTY */ %prec KEYS { $$ = false; }
;
json_name_and_value_list:
@@ -17182,6 +17236,7 @@ unreserved_keyword:
| ROWS
| RULE
| SAVEPOINT
+ | SCALAR
| SCHEMA
| SCHEMAS
| SCROLL
@@ -17784,6 +17839,7 @@ bare_label_keyword:
| ROWS
| RULE
| SAVEPOINT
+ | SCALAR
| SCHEMA
| SCHEMAS
| SCROLL
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 737b479f54..4c99dd1dec 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -83,6 +83,7 @@ static Node *transformJsonArrayQueryConstructor(ParseState *pstate,
JsonArrayQueryConstructor *ctor);
static Node *transformJsonObjectAgg(ParseState *pstate, JsonObjectAgg *agg);
static Node *transformJsonArrayAgg(ParseState *pstate, JsonArrayAgg *agg);
+static Node *transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *p);
static Node *make_row_comparison_op(ParseState *pstate, List *opname,
List *largs, List *rargs, int location);
static Node *make_row_distinct_op(ParseState *pstate, List *opname,
@@ -325,6 +326,10 @@ transformExprRecurse(ParseState *pstate, Node *expr)
result = transformJsonArrayAgg(pstate, (JsonArrayAgg *) expr);
break;
+ case T_JsonIsPredicate:
+ result = transformJsonIsPredicate(pstate, (JsonIsPredicate *) expr);
+ break;
+
default:
/* should not reach here */
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
@@ -3818,3 +3823,74 @@ transformJsonArrayConstructor(ParseState *pstate, JsonArrayConstructor *ctor)
returning, false, ctor->absent_on_null,
ctor->location);
}
+
+static Node *
+transformJsonParseArg(ParseState *pstate, Node *jsexpr, JsonFormat *format,
+ Oid *exprtype)
+{
+ Node *raw_expr = transformExprRecurse(pstate, jsexpr);
+ Node *expr = raw_expr;
+
+ *exprtype = exprType(expr);
+
+ /* prepare input document */
+ if (*exprtype == BYTEAOID)
+ {
+ JsonValueExpr *jve;
+
+ expr = makeCaseTestExpr(raw_expr);
+ expr = makeJsonByteaToTextConversion(expr, format, exprLocation(expr));
+ *exprtype = TEXTOID;
+
+ jve = makeJsonValueExpr((Expr *) raw_expr, format);
+
+ jve->formatted_expr = (Expr *) expr;
+ expr = (Node *) jve;
+ }
+ else
+ {
+ char typcategory;
+ bool typispreferred;
+
+ get_type_category_preferred(*exprtype, &typcategory, &typispreferred);
+
+ if (*exprtype == UNKNOWNOID || typcategory == TYPCATEGORY_STRING)
+ {
+ expr = coerce_to_target_type(pstate, (Node *) expr, *exprtype,
+ TEXTOID, -1,
+ COERCION_IMPLICIT,
+ COERCE_IMPLICIT_CAST, -1);
+ *exprtype = TEXTOID;
+ }
+
+ if (format->encoding != JS_ENC_DEFAULT)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ parser_errposition(pstate, format->location),
+ errmsg("cannot use JSON FORMAT ENCODING clause for non-bytea input types")));
+ }
+
+ return expr;
+}
+
+/*
+ * Transform IS JSON predicate.
+ */
+static Node *
+transformJsonIsPredicate(ParseState *pstate, JsonIsPredicate *pred)
+{
+ Oid exprtype;
+ Node *expr = transformJsonParseArg(pstate, pred->expr, pred->format,
+ &exprtype);
+
+ /* make resulting expression */
+ if (exprtype != TEXTOID && exprtype != JSONOID && exprtype != JSONBOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot use type %s in IS JSON predicate",
+ format_type_be(exprtype))));
+
+ /* This intentionally(?) drops the format clause. */
+ return makeJsonIsPredicate(expr, NULL, pred->item_type,
+ pred->unique_keys, pred->location);
+}
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index 65eb087657..f97d7c3572 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -237,6 +237,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
case ORDINALITY:
cur_token = WITH_LA;
break;
+ case UNIQUE:
+ cur_token = WITH_UNIQUE_LA;
+ break;
}
break;
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index dcd2bb2234..49080e5fbf 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -62,6 +62,23 @@ typedef struct JsonUniqueHashEntry
int object_id;
} JsonUniqueHashEntry;
+/* Stack element for key uniqueness check during JSON parsing */
+typedef struct JsonUniqueStackEntry
+{
+ struct JsonUniqueStackEntry *parent;
+ int object_id;
+} JsonUniqueStackEntry;
+
+/* Context struct for key uniqueness check during JSON parsing */
+typedef struct JsonUniqueParsingState
+{
+ JsonLexContext *lex;
+ JsonUniqueCheckState check;
+ JsonUniqueStackEntry *stack;
+ int id_counter;
+ bool unique;
+} JsonUniqueParsingState;
+
/* Context struct for key uniqueness check during JSON building */
typedef struct JsonUniqueBuilderState
{
@@ -1648,6 +1665,110 @@ escape_json(StringInfo buf, const char *str)
appendStringInfoCharMacro(buf, '"');
}
+/* Semantic actions for key uniqueness check */
+static JsonParseErrorType
+json_unique_object_start(void *_state)
+{
+ JsonUniqueParsingState *state = _state;
+ JsonUniqueStackEntry *entry;
+
+ if (!state->unique)
+ return JSON_SUCCESS;
+
+ /* push object entry to stack */
+ entry = palloc(sizeof(*entry));
+ entry->object_id = state->id_counter++;
+ entry->parent = state->stack;
+ state->stack = entry;
+
+ return JSON_SUCCESS;
+}
+
+static JsonParseErrorType
+json_unique_object_end(void *_state)
+{
+ JsonUniqueParsingState *state = _state;
+ JsonUniqueStackEntry *entry;
+
+ if (!state->unique)
+ return JSON_SUCCESS;
+
+ entry = state->stack;
+ state->stack = entry->parent; /* pop object from stack */
+ pfree(entry);
+ return JSON_SUCCESS;
+}
+
+static JsonParseErrorType
+json_unique_object_field_start(void *_state, char *field, bool isnull)
+{
+ JsonUniqueParsingState *state = _state;
+ JsonUniqueStackEntry *entry;
+
+ if (!state->unique)
+ return JSON_SUCCESS;
+
+ /* find key collision in the current object */
+ if (json_unique_check_key(&state->check, field, state->stack->object_id))
+ return JSON_SUCCESS;
+
+ state->unique = false;
+
+ /* pop all objects entries */
+ while ((entry = state->stack))
+ {
+ state->stack = entry->parent;
+ pfree(entry);
+ }
+ return JSON_SUCCESS;
+}
+
+/* Validate JSON text and additionally check key uniqueness */
+bool
+json_validate(text *json, bool check_unique_keys, bool throw_error)
+{
+ JsonLexContext *lex = makeJsonLexContext(json, check_unique_keys);
+ JsonSemAction uniqueSemAction = {0};
+ JsonUniqueParsingState state;
+ JsonParseErrorType result;
+
+ if (check_unique_keys)
+ {
+ state.lex = lex;
+ state.stack = NULL;
+ state.id_counter = 0;
+ state.unique = true;
+ json_unique_check_init(&state.check);
+
+ uniqueSemAction.semstate = &state;
+ uniqueSemAction.object_start = json_unique_object_start;
+ uniqueSemAction.object_field_start = json_unique_object_field_start;
+ uniqueSemAction.object_end = json_unique_object_end;
+ }
+
+ result = pg_parse_json(lex, check_unique_keys ? &uniqueSemAction : &nullSemAction);
+
+ if (result != JSON_SUCCESS)
+ {
+ if (throw_error)
+ json_errsave_error(result, lex, NULL);
+
+ return false; /* invalid json */
+ }
+
+ if (check_unique_keys && !state.unique)
+ {
+ if (throw_error)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE),
+ errmsg("duplicate JSON object key value")));
+
+ return false; /* not unique keys */
+ }
+
+ return true; /* ok */
+}
+
/*
* SQL function json_typeof(json) -> text
*
@@ -1663,21 +1784,18 @@ escape_json(StringInfo buf, const char *str)
Datum
json_typeof(PG_FUNCTION_ARGS)
{
- text *json;
-
- JsonLexContext *lex;
- JsonTokenType tok;
+ text *json = PG_GETARG_TEXT_PP(0);
+ JsonLexContext *lex = makeJsonLexContext(json, false);
char *type;
+ JsonTokenType tok;
JsonParseErrorType result;
- json = PG_GETARG_TEXT_PP(0);
- lex = makeJsonLexContext(json, false);
-
/* Lex exactly one token from the input and check its type. */
result = json_lex(lex);
if (result != JSON_SUCCESS)
json_errsave_error(result, lex, NULL);
tok = lex->token_type;
+
switch (tok)
{
case JSON_TOKEN_OBJECT_START:
diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c
index 7a36f74dad..4c5abaff25 100644
--- a/src/backend/utils/adt/jsonfuncs.c
+++ b/src/backend/utils/adt/jsonfuncs.c
@@ -5665,3 +5665,23 @@ transform_string_values_scalar(void *state, char *token, JsonTokenType tokentype
return JSON_SUCCESS;
}
+
+JsonTokenType
+json_get_first_token(text *json, bool throw_error)
+{
+ JsonLexContext *lex;
+ JsonParseErrorType result;
+
+ lex = makeJsonLexContext(json, false);
+
+ /* Lex exactly one token from the input and check its type. */
+ result = json_lex(lex);
+
+ if (result == JSON_SUCCESS)
+ return lex->token_type;
+
+ if (throw_error)
+ json_errsave_error(result, lex, NULL);
+
+ return JSON_TOKEN_INVALID; /* invalid json */
+}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 1c078d700d..461735e84f 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -8227,6 +8227,7 @@ isSimpleNode(Node *node, Node *parentNode, int prettyFlags)
case T_NullTest:
case T_BooleanTest:
case T_DistinctExpr:
+ case T_JsonIsPredicate:
switch (nodeTag(parentNode))
{
case T_FuncExpr:
@@ -9530,6 +9531,42 @@ get_rule_expr(Node *node, deparse_context *context,
get_json_constructor((JsonConstructorExpr *) node, context, false);
break;
+ case T_JsonIsPredicate:
+ {
+ JsonIsPredicate *pred = (JsonIsPredicate *) node;
+
+ if (!PRETTY_PAREN(context))
+ appendStringInfoChar(context->buf, '(');
+
+ get_rule_expr_paren(pred->expr, context, true, node);
+
+ appendStringInfoString(context->buf, " IS JSON");
+
+ /* TODO: handle FORMAT clause */
+
+ switch (pred->item_type)
+ {
+ case JS_TYPE_SCALAR:
+ appendStringInfoString(context->buf, " SCALAR");
+ break;
+ case JS_TYPE_ARRAY:
+ appendStringInfoString(context->buf, " ARRAY");
+ break;
+ case JS_TYPE_OBJECT:
+ appendStringInfoString(context->buf, " OBJECT");
+ break;
+ default:
+ break;
+ }
+
+ if (pred->unique_keys)
+ appendStringInfoString(context->buf, " WITH UNIQUE KEYS");
+
+ if (!PRETTY_PAREN(context))
+ appendStringInfoChar(context->buf, ')');
+ }
+ break;
+
case T_List:
{
char *sep;
diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 2419f2b11d..81e78230e4 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -57,6 +57,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 202303301
+#define CATALOG_VERSION_NO 202303311
#endif
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 53e2c9a467..ea3ac10876 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -236,6 +236,7 @@ typedef enum ExprEvalOp
EEOP_HASHED_SCALARARRAYOP,
EEOP_XMLEXPR,
EEOP_JSON_CONSTRUCTOR,
+ EEOP_IS_JSON,
EEOP_AGGREF,
EEOP_GROUPING_FUNC,
EEOP_WINDOW_FUNC,
@@ -675,6 +676,12 @@ typedef struct ExprEvalStep
int setoff;
} agg_trans;
+ /* for EEOP_IS_JSON */
+ struct
+ {
+ JsonIsPredicate *pred; /* original expression node */
+ } is_json;
+
} d;
} ExprEvalStep;
@@ -789,6 +796,7 @@ extern void ExecEvalConstraintCheck(ExprState *state, ExprEvalStep *op);
extern void ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op);
extern void ExecEvalJsonConstructor(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
+extern void ExecEvalJsonIsPredicate(ExprState *state, ExprEvalStep *op);
extern void ExecEvalGroupingFunc(ExprState *state, ExprEvalStep *op);
extern void ExecEvalSubPlan(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
diff --git a/src/include/nodes/makefuncs.h b/src/include/nodes/makefuncs.h
index 50aa00e0c1..06d991b725 100644
--- a/src/include/nodes/makefuncs.h
+++ b/src/include/nodes/makefuncs.h
@@ -112,6 +112,9 @@ extern JsonFormat *makeJsonFormat(JsonFormatType type, JsonEncoding encoding,
int location);
extern JsonValueExpr *makeJsonValueExpr(Expr *expr, JsonFormat *format);
extern Node *makeJsonKeyValue(Node *key, Node *value);
+extern Node *makeJsonIsPredicate(Node *expr, JsonFormat *format,
+ JsonValueType item_type, bool unique_keys,
+ int location);
extern JsonEncoding makeJsonEncoding(char *name);
#endif /* MAKEFUNC_H */
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 1764b61cdb..be9c29f0bf 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -1583,6 +1583,32 @@ typedef struct JsonConstructorExpr
int location;
} JsonConstructorExpr;
+/*
+ * JsonValueType -
+ * representation of JSON item type in IS JSON predicate
+ */
+typedef enum JsonValueType
+{
+ JS_TYPE_ANY, /* IS JSON [VALUE] */
+ JS_TYPE_OBJECT, /* IS JSON OBJECT */
+ JS_TYPE_ARRAY, /* IS JSON ARRAY */
+ JS_TYPE_SCALAR /* IS JSON SCALAR */
+} JsonValueType;
+
+/*
+ * JsonIsPredicate -
+ * representation of IS JSON predicate
+ */
+typedef struct JsonIsPredicate
+{
+ NodeTag type;
+ Node *expr; /* subject expression */
+ JsonFormat *format; /* FORMAT clause, if specified */
+ JsonValueType item_type; /* JSON item type */
+ bool unique_keys; /* check key uniqueness? */
+ int location; /* token location, or -1 if unknown */
+} JsonIsPredicate;
+
/* ----------------
* NullTest
*
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 868f389a04..f5b2e61ca5 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -376,6 +376,7 @@ PG_KEYWORD("row", ROW, COL_NAME_KEYWORD, BARE_LABEL)
PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("savepoint", SAVEPOINT, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("scalar", SCALAR, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("schema", SCHEMA, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("schemas", SCHEMAS, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("scroll", SCROLL, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/utils/json.h b/src/include/utils/json.h
index b75f7d929d..35a9a5545d 100644
--- a/src/include/utils/json.h
+++ b/src/include/utils/json.h
@@ -26,5 +26,6 @@ extern Datum json_build_object_worker(int nargs, Datum *args, bool *nulls,
bool unique_keys);
extern Datum json_build_array_worker(int nargs, Datum *args, bool *nulls,
Oid *types, bool absent_on_null);
+extern bool json_validate(text *json, bool check_unique_keys, bool throw_error);
#endif /* JSON_H */
diff --git a/src/include/utils/jsonfuncs.h b/src/include/utils/jsonfuncs.h
index fc610f6503..a85203d4a4 100644
--- a/src/include/utils/jsonfuncs.h
+++ b/src/include/utils/jsonfuncs.h
@@ -50,6 +50,9 @@ extern bool pg_parse_json_or_errsave(JsonLexContext *lex, JsonSemAction *sem,
extern void json_errsave_error(JsonParseErrorType error, JsonLexContext *lex,
struct Node *escontext);
+/* get first JSON token */
+extern JsonTokenType json_get_first_token(text *json, bool throw_error);
+
extern uint32 parse_jsonb_index_flags(Jsonb *jb);
extern void iterate_jsonb_values(Jsonb *jb, uint32 flags, void *state,
JsonIterateStringValuesAction action);
diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl
index faeb460ef5..8a3855b4e2 100644
--- a/src/interfaces/ecpg/preproc/parse.pl
+++ b/src/interfaces/ecpg/preproc/parse.pl
@@ -59,6 +59,7 @@ my %replace_string = (
'NOT_LA' => 'not',
'NULLS_LA' => 'nulls',
'WITH_LA' => 'with',
+ 'WITH_UNIQUE_LA' => 'with',
'WITHOUT_LA' => 'without',
'TYPECAST' => '::',
'DOT_DOT' => '..',
diff --git a/src/interfaces/ecpg/preproc/parser.c b/src/interfaces/ecpg/preproc/parser.c
index a40f4bef09..30010a5706 100644
--- a/src/interfaces/ecpg/preproc/parser.c
+++ b/src/interfaces/ecpg/preproc/parser.c
@@ -155,6 +155,9 @@ filtered_base_yylex(void)
case ORDINALITY:
cur_token = WITH_LA;
break;
+ case UNIQUE:
+ cur_token = WITH_UNIQUE_LA;
+ break;
}
break;
diff --git a/src/interfaces/ecpg/test/expected/sql-sqljson.c b/src/interfaces/ecpg/test/expected/sql-sqljson.c
index 64784542ed..a2c49b54f9 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.c
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.c
@@ -100,103 +100,132 @@ main ()
{
/* exec sql begin declare section */
+
#line 12 "sqljson.pgc"
char json [ 1024 ] ;
-/* exec sql end declare section */
+
#line 13 "sqljson.pgc"
+ bool is_json [ 8 ] ;
+/* exec sql end declare section */
+#line 14 "sqljson.pgc"
ECPGdebug (1, stderr);
{ ECPGconnect(__LINE__, 0, "ecpg1_regression" , NULL, NULL , NULL, 0);
-#line 17 "sqljson.pgc"
+#line 18 "sqljson.pgc"
if (sqlca.sqlcode < 0) sqlprint();}
-#line 17 "sqljson.pgc"
+#line 18 "sqljson.pgc"
{ ECPGsetcommit(__LINE__, "on", NULL);
-#line 18 "sqljson.pgc"
+#line 19 "sqljson.pgc"
if (sqlca.sqlcode < 0) sqlprint();}
-#line 18 "sqljson.pgc"
+#line 19 "sqljson.pgc"
{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_object ( returning text )", ECPGt_EOIT,
ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char),
ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
-#line 20 "sqljson.pgc"
+#line 21 "sqljson.pgc"
if (sqlca.sqlcode < 0) sqlprint();}
-#line 20 "sqljson.pgc"
+#line 21 "sqljson.pgc"
printf("Found json=%s\n", json);
{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_object ( returning text format json )", ECPGt_EOIT,
ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char),
ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
-#line 23 "sqljson.pgc"
+#line 24 "sqljson.pgc"
if (sqlca.sqlcode < 0) sqlprint();}
-#line 23 "sqljson.pgc"
+#line 24 "sqljson.pgc"
printf("Found json=%s\n", json);
{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_array ( returning jsonb )", ECPGt_EOIT,
ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char),
ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
-#line 26 "sqljson.pgc"
+#line 27 "sqljson.pgc"
if (sqlca.sqlcode < 0) sqlprint();}
-#line 26 "sqljson.pgc"
+#line 27 "sqljson.pgc"
printf("Found json=%s\n", json);
{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_array ( returning jsonb format json )", ECPGt_EOIT,
ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char),
ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
-#line 29 "sqljson.pgc"
+#line 30 "sqljson.pgc"
if (sqlca.sqlcode < 0) sqlprint();}
-#line 29 "sqljson.pgc"
+#line 30 "sqljson.pgc"
printf("Found json=%s\n", json);
{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_object ( 1 : 1 , '1' : null with unique )", ECPGt_EOIT,
ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char),
ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
-#line 32 "sqljson.pgc"
+#line 33 "sqljson.pgc"
if (sqlca.sqlcode < 0) sqlprint();}
-#line 32 "sqljson.pgc"
+#line 33 "sqljson.pgc"
// error
{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_object ( 1 : 1 , '2' : null , 1 : '2' absent on null without unique keys )", ECPGt_EOIT,
ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char),
ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
-#line 35 "sqljson.pgc"
+#line 36 "sqljson.pgc"
if (sqlca.sqlcode < 0) sqlprint();}
-#line 35 "sqljson.pgc"
+#line 36 "sqljson.pgc"
printf("Found json=%s\n", json);
{ ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "select json_object ( 1 : 1 , '2' : null absent on null without unique returning jsonb )", ECPGt_EOIT,
ECPGt_char,(json),(long)1024,(long)1,(1024)*sizeof(char),
ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
-#line 38 "sqljson.pgc"
+#line 39 "sqljson.pgc"
if (sqlca.sqlcode < 0) sqlprint();}
-#line 38 "sqljson.pgc"
+#line 39 "sqljson.pgc"
printf("Found json=%s\n", json);
- { ECPGdisconnect(__LINE__, "CURRENT");
-#line 41 "sqljson.pgc"
+ { ECPGdo(__LINE__, 0, 1, NULL, 0, ECPGst_normal, "with val ( js ) as ( values ( '{ \"a\": 1, \"b\": [{ \"a\": 1, \"b\": 0, \"a\": 2 }] }' ) ) select js is json \"IS JSON\" , js is not json \"IS NOT JSON\" , js is json value \"IS VALUE\" , js is json object \"IS OBJECT\" , js is json array \"IS ARRAY\" , js is json scalar \"IS SCALAR\" , js is json without unique keys \"WITHOUT UNIQUE\" , js is json with unique keys \"WITH UNIQUE\" from val", ECPGt_EOIT,
+ ECPGt_bool,&(is_json[0]),(long)1,(long)1,sizeof(bool),
+ ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L,
+ ECPGt_bool,&(is_json[1]),(long)1,(long)1,sizeof(bool),
+ ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L,
+ ECPGt_bool,&(is_json[2]),(long)1,(long)1,sizeof(bool),
+ ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L,
+ ECPGt_bool,&(is_json[3]),(long)1,(long)1,sizeof(bool),
+ ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L,
+ ECPGt_bool,&(is_json[4]),(long)1,(long)1,sizeof(bool),
+ ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L,
+ ECPGt_bool,&(is_json[5]),(long)1,(long)1,sizeof(bool),
+ ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L,
+ ECPGt_bool,&(is_json[6]),(long)1,(long)1,sizeof(bool),
+ ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L,
+ ECPGt_bool,&(is_json[7]),(long)1,(long)1,sizeof(bool),
+ ECPGt_NO_INDICATOR, NULL , 0L, 0L, 0L, ECPGt_EORT);
+#line 54 "sqljson.pgc"
if (sqlca.sqlcode < 0) sqlprint();}
-#line 41 "sqljson.pgc"
+#line 54 "sqljson.pgc"
+
+ 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");
+#line 58 "sqljson.pgc"
+
+if (sqlca.sqlcode < 0) sqlprint();}
+#line 58 "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 907f773eb9..1252cb3b66 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stderr
@@ -2,68 +2,90 @@
[NO_PID]: sqlca: code: 0, state: 00000
[NO_PID]: ECPGconnect: opening database ecpg1_regression on <DEFAULT> port <DEFAULT>
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ECPGsetcommit on line 18: action "on"; connection "ecpg1_regression"
+[NO_PID]: ECPGsetcommit on line 19: action "on"; connection "ecpg1_regression"
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_execute on line 20: query: select json_object ( returning text ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: ecpg_execute on line 21: query: select json_object ( returning text ); with 0 parameter(s) on connection ecpg1_regression
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_execute on line 20: using PQexec
+[NO_PID]: ecpg_execute on line 21: using PQexec
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_process_output on line 20: correctly got 1 tuples with 1 fields
+[NO_PID]: ecpg_process_output on line 21: correctly got 1 tuples with 1 fields
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_get_data on line 20: RESULT: {} offset: -1; array: no
+[NO_PID]: ecpg_get_data on line 21: RESULT: {} offset: -1; array: no
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_execute on line 23: query: select json_object ( returning text format json ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: ecpg_execute on line 24: query: select json_object ( returning text format json ); with 0 parameter(s) on connection ecpg1_regression
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_execute on line 23: using PQexec
+[NO_PID]: ecpg_execute on line 24: using PQexec
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_process_output on line 23: correctly got 1 tuples with 1 fields
+[NO_PID]: ecpg_process_output on line 24: correctly got 1 tuples with 1 fields
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_get_data on line 23: RESULT: {} offset: -1; array: no
+[NO_PID]: ecpg_get_data on line 24: RESULT: {} offset: -1; array: no
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_execute on line 26: query: select json_array ( returning jsonb ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: ecpg_execute on line 27: query: select json_array ( returning jsonb ); with 0 parameter(s) on connection ecpg1_regression
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_execute on line 26: using PQexec
+[NO_PID]: ecpg_execute on line 27: using PQexec
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_process_output on line 26: correctly got 1 tuples with 1 fields
+[NO_PID]: ecpg_process_output on line 27: correctly got 1 tuples with 1 fields
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_is_type_an_array on line 26: type (3802); C (1); array (no)
+[NO_PID]: ecpg_is_type_an_array on line 27: type (3802); C (1); array (no)
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_get_data on line 26: RESULT: [] offset: -1; array: no
+[NO_PID]: ecpg_get_data on line 27: RESULT: [] offset: -1; array: no
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_execute on line 29: query: select json_array ( returning jsonb format json ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: ecpg_execute on line 30: query: select json_array ( returning jsonb format json ); with 0 parameter(s) on connection ecpg1_regression
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_execute on line 29: using PQexec
+[NO_PID]: ecpg_execute on line 30: using PQexec
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_process_output on line 29: correctly got 1 tuples with 1 fields
+[NO_PID]: ecpg_process_output on line 30: correctly got 1 tuples with 1 fields
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_get_data on line 29: RESULT: [] offset: -1; array: no
+[NO_PID]: ecpg_get_data on line 30: RESULT: [] offset: -1; array: no
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_execute on line 32: query: select json_object ( 1 : 1 , '1' : null with unique ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: ecpg_execute on line 33: query: select json_object ( 1 : 1 , '1' : null with unique ); with 0 parameter(s) on connection ecpg1_regression
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_execute on line 32: using PQexec
+[NO_PID]: ecpg_execute on line 33: using PQexec
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_check_PQresult on line 32: bad response - ERROR: duplicate JSON key "1"
+[NO_PID]: ecpg_check_PQresult on line 33: bad response - ERROR: duplicate JSON key "1"
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: raising sqlstate 22030 (sqlcode -400): duplicate JSON key "1" on line 32
+[NO_PID]: raising sqlstate 22030 (sqlcode -400): duplicate JSON key "1" on line 33
[NO_PID]: sqlca: code: -400, state: 22030
-SQL error: duplicate JSON key "1" on line 32
-[NO_PID]: ecpg_execute on line 35: query: select json_object ( 1 : 1 , '2' : null , 1 : '2' absent on null without unique keys ); with 0 parameter(s) on connection ecpg1_regression
+SQL error: duplicate JSON key "1" on line 33
+[NO_PID]: ecpg_execute on line 36: query: select json_object ( 1 : 1 , '2' : null , 1 : '2' absent on null without unique keys ); with 0 parameter(s) on connection ecpg1_regression
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_execute on line 35: using PQexec
+[NO_PID]: ecpg_execute on line 36: using PQexec
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_process_output on line 35: correctly got 1 tuples with 1 fields
+[NO_PID]: ecpg_process_output on line 36: correctly got 1 tuples with 1 fields
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_is_type_an_array on line 35: type (114); C (1); array (no)
+[NO_PID]: ecpg_is_type_an_array on line 36: type (114); C (1); array (no)
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_get_data on line 35: RESULT: {"1" : 1, "1" : "2"} offset: -1; array: no
+[NO_PID]: ecpg_get_data on line 36: RESULT: {"1" : 1, "1" : "2"} offset: -1; array: no
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_execute on line 38: query: select json_object ( 1 : 1 , '2' : null absent on null without unique returning jsonb ); with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: ecpg_execute on line 39: query: select json_object ( 1 : 1 , '2' : null absent on null without unique returning jsonb ); with 0 parameter(s) on connection ecpg1_regression
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_execute on line 38: using PQexec
+[NO_PID]: ecpg_execute on line 39: using PQexec
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_process_output on line 38: correctly got 1 tuples with 1 fields
+[NO_PID]: ecpg_process_output on line 39: correctly got 1 tuples with 1 fields
[NO_PID]: sqlca: code: 0, state: 00000
-[NO_PID]: ecpg_get_data on line 38: RESULT: {"1": 1} offset: -1; array: no
+[NO_PID]: ecpg_get_data on line 39: RESULT: {"1": 1} offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 42: query: with val ( js ) as ( values ( '{ "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] }' ) ) select js is json "IS JSON" , js is not json "IS NOT JSON" , js is json value "IS VALUE" , js is json object "IS OBJECT" , js is json array "IS ARRAY" , js is json scalar "IS SCALAR" , js is json without unique keys "WITHOUT UNIQUE" , js is json with unique keys "WITH UNIQUE" from val; with 0 parameter(s) on connection ecpg1_regression
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_execute on line 42: using PQexec
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_process_output on line 42: correctly got 1 tuples with 8 fields
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 42: RESULT: t offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 42: RESULT: f offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 42: RESULT: t offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 42: RESULT: t offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 42: RESULT: f offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 42: RESULT: f offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 42: RESULT: t offset: -1; array: no
+[NO_PID]: sqlca: code: 0, state: 00000
+[NO_PID]: ecpg_get_data on line 42: RESULT: f offset: -1; array: no
[NO_PID]: sqlca: code: 0, state: 00000
[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 aae052a2b9..558901c406 100644
--- a/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
+++ b/src/interfaces/ecpg/test/expected/sql-sqljson.stdout
@@ -4,3 +4,11 @@ Found json=[]
Found json=[]
Found json={"1" : 1, "1" : "2"}
Found json={"1": 1}
+Found is_json[0]: true
+Found is_json[1]: false
+Found is_json[2]: true
+Found is_json[3]: true
+Found is_json[4]: false
+Found is_json[5]: false
+Found is_json[6]: true
+Found is_json[7]: false
diff --git a/src/interfaces/ecpg/test/sql/sqljson.pgc b/src/interfaces/ecpg/test/sql/sqljson.pgc
index 6a582b5b10..a005503834 100644
--- a/src/interfaces/ecpg/test/sql/sqljson.pgc
+++ b/src/interfaces/ecpg/test/sql/sqljson.pgc
@@ -10,6 +10,7 @@ main ()
{
EXEC SQL BEGIN DECLARE SECTION;
char json[1024];
+ bool is_json[8];
EXEC SQL END DECLARE SECTION;
ECPGdebug (1, stderr);
@@ -38,6 +39,22 @@ EXEC SQL END DECLARE SECTION;
EXEC SQL SELECT JSON_OBJECT(1: 1, '2': NULL ABSENT ON NULL WITHOUT UNIQUE RETURNING jsonb) INTO :json;
printf("Found json=%s\n", json);
+ EXEC SQL WITH val (js) AS (VALUES ('{ "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] }'))
+ SELECT
+ js IS JSON "IS JSON",
+ js IS NOT JSON "IS NOT JSON",
+ js IS JSON VALUE "IS VALUE",
+ js IS JSON OBJECT "IS OBJECT",
+ js IS JSON ARRAY "IS ARRAY",
+ js IS JSON SCALAR "IS SCALAR",
+ js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE",
+ js IS JSON WITH UNIQUE KEYS "WITH UNIQUE"
+ INTO :is_json[0], :is_json[1], :is_json[2], :is_json[3], :is_json[4],
+ :is_json[5], :is_json[6], :is_json[7]
+ FROM val;
+ for (int i = 0; i < sizeof(is_json); i++)
+ printf("Found is_json[%d]: %s\n", i, is_json[i] ? "true" : "false");
+
EXEC SQL DISCONNECT;
return 0;
diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out
index c4bfe80ba9..d73c7e2c6c 100644
--- a/src/test/regress/expected/sqljson.out
+++ b/src/test/regress/expected/sqljson.out
@@ -754,3 +754,201 @@ CREATE OR REPLACE VIEW public.json_array_subquery_view AS
FROM ( SELECT foo.i
FROM ( VALUES (1), (2), (NULL::integer), (4)) foo(i)) q(a)) AS "json_array"
DROP VIEW json_array_subquery_view;
+-- IS JSON predicate
+SELECT NULL IS JSON;
+ ?column?
+----------
+
+(1 row)
+
+SELECT NULL IS NOT JSON;
+ ?column?
+----------
+
+(1 row)
+
+SELECT NULL::json IS JSON;
+ ?column?
+----------
+
+(1 row)
+
+SELECT NULL::jsonb IS JSON;
+ ?column?
+----------
+
+(1 row)
+
+SELECT NULL::text IS JSON;
+ ?column?
+----------
+
+(1 row)
+
+SELECT NULL::bytea IS JSON;
+ ?column?
+----------
+
+(1 row)
+
+SELECT NULL::int IS JSON;
+ERROR: cannot use type integer in IS JSON predicate
+SELECT '' IS JSON;
+ ?column?
+----------
+ f
+(1 row)
+
+SELECT bytea '\x00' IS JSON;
+ERROR: invalid byte sequence for encoding "UTF8": 0x00
+CREATE TABLE test_is_json (js text);
+INSERT INTO test_is_json VALUES
+ (NULL),
+ (''),
+ ('123'),
+ ('"aaa "'),
+ ('true'),
+ ('null'),
+ ('[]'),
+ ('[1, "2", {}]'),
+ ('{}'),
+ ('{ "a": 1, "b": null }'),
+ ('{ "a": 1, "a": null }'),
+ ('{ "a": 1, "b": [{ "a": 1 }, { "a": 2 }] }'),
+ ('{ "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] }'),
+ ('aaa'),
+ ('{a:1}'),
+ ('["a",]');
+SELECT
+ js,
+ js IS JSON "IS JSON",
+ js IS NOT JSON "IS NOT JSON",
+ js IS JSON VALUE "IS VALUE",
+ js IS JSON OBJECT "IS OBJECT",
+ js IS JSON ARRAY "IS ARRAY",
+ js IS JSON SCALAR "IS SCALAR",
+ js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE",
+ js IS JSON WITH UNIQUE KEYS "WITH UNIQUE"
+FROM
+ test_is_json;
+ js | IS JSON | IS NOT JSON | IS VALUE | IS OBJECT | IS ARRAY | IS SCALAR | WITHOUT UNIQUE | WITH UNIQUE
+-----------------------------------------------+---------+-------------+----------+-----------+----------+-----------+----------------+-------------
+ | | | | | | | |
+ | f | t | f | f | f | f | f | f
+ 123 | t | f | t | f | f | t | t | t
+ "aaa " | t | f | t | f | f | t | t | t
+ true | t | f | t | f | f | t | t | t
+ null | t | f | t | f | f | t | t | t
+ [] | t | f | t | f | t | f | t | t
+ [1, "2", {}] | t | f | t | f | t | f | t | t
+ {} | t | f | t | t | f | f | t | t
+ { "a": 1, "b": null } | t | f | t | t | f | f | t | t
+ { "a": 1, "a": null } | t | f | t | t | f | f | t | f
+ { "a": 1, "b": [{ "a": 1 }, { "a": 2 }] } | t | f | t | t | f | f | t | t
+ { "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] } | t | f | t | t | f | f | t | f
+ aaa | f | t | f | f | f | f | f | f
+ {a:1} | f | t | f | f | f | f | f | f
+ ["a",] | f | t | f | f | f | f | f | f
+(16 rows)
+
+SELECT
+ js,
+ js IS JSON "IS JSON",
+ js IS NOT JSON "IS NOT JSON",
+ js IS JSON VALUE "IS VALUE",
+ js IS JSON OBJECT "IS OBJECT",
+ js IS JSON ARRAY "IS ARRAY",
+ js IS JSON SCALAR "IS SCALAR",
+ js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE",
+ js IS JSON WITH UNIQUE KEYS "WITH UNIQUE"
+FROM
+ (SELECT js::json FROM test_is_json WHERE js IS JSON) foo(js);
+ js | IS JSON | IS NOT JSON | IS VALUE | IS OBJECT | IS ARRAY | IS SCALAR | WITHOUT UNIQUE | WITH UNIQUE
+-----------------------------------------------+---------+-------------+----------+-----------+----------+-----------+----------------+-------------
+ 123 | t | f | t | f | f | t | t | t
+ "aaa " | t | f | t | f | f | t | t | t
+ true | t | f | t | f | f | t | t | t
+ null | t | f | t | f | f | t | t | t
+ [] | t | f | t | f | t | f | t | t
+ [1, "2", {}] | t | f | t | f | t | f | t | t
+ {} | t | f | t | t | f | f | t | t
+ { "a": 1, "b": null } | t | f | t | t | f | f | t | t
+ { "a": 1, "a": null } | t | f | t | t | f | f | t | f
+ { "a": 1, "b": [{ "a": 1 }, { "a": 2 }] } | t | f | t | t | f | f | t | t
+ { "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] } | t | f | t | t | f | f | t | f
+(11 rows)
+
+SELECT
+ js0,
+ js IS JSON "IS JSON",
+ js IS NOT JSON "IS NOT JSON",
+ js IS JSON VALUE "IS VALUE",
+ js IS JSON OBJECT "IS OBJECT",
+ js IS JSON ARRAY "IS ARRAY",
+ js IS JSON SCALAR "IS SCALAR",
+ js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE",
+ js IS JSON WITH UNIQUE KEYS "WITH UNIQUE"
+FROM
+ (SELECT js, js::bytea FROM test_is_json WHERE js IS JSON) foo(js0, js);
+ js0 | IS JSON | IS NOT JSON | IS VALUE | IS OBJECT | IS ARRAY | IS SCALAR | WITHOUT UNIQUE | WITH UNIQUE
+-----------------------------------------------+---------+-------------+----------+-----------+----------+-----------+----------------+-------------
+ 123 | t | f | t | f | f | t | t | t
+ "aaa " | t | f | t | f | f | t | t | t
+ true | t | f | t | f | f | t | t | t
+ null | t | f | t | f | f | t | t | t
+ [] | t | f | t | f | t | f | t | t
+ [1, "2", {}] | t | f | t | f | t | f | t | t
+ {} | t | f | t | t | f | f | t | t
+ { "a": 1, "b": null } | t | f | t | t | f | f | t | t
+ { "a": 1, "a": null } | t | f | t | t | f | f | t | f
+ { "a": 1, "b": [{ "a": 1 }, { "a": 2 }] } | t | f | t | t | f | f | t | t
+ { "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] } | t | f | t | t | f | f | t | f
+(11 rows)
+
+SELECT
+ js,
+ js IS JSON "IS JSON",
+ js IS NOT JSON "IS NOT JSON",
+ js IS JSON VALUE "IS VALUE",
+ js IS JSON OBJECT "IS OBJECT",
+ js IS JSON ARRAY "IS ARRAY",
+ js IS JSON SCALAR "IS SCALAR",
+ js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE",
+ js IS JSON WITH UNIQUE KEYS "WITH UNIQUE"
+FROM
+ (SELECT js::jsonb FROM test_is_json WHERE js IS JSON) foo(js);
+ js | IS JSON | IS NOT JSON | IS VALUE | IS OBJECT | IS ARRAY | IS SCALAR | WITHOUT UNIQUE | WITH UNIQUE
+-------------------------------------+---------+-------------+----------+-----------+----------+-----------+----------------+-------------
+ 123 | t | f | t | f | f | t | t | t
+ "aaa " | t | f | t | f | f | t | t | t
+ true | t | f | t | f | f | t | t | t
+ null | t | f | t | f | f | t | t | t
+ [] | t | f | t | f | t | f | t | t
+ [1, "2", {}] | t | f | t | f | t | f | t | t
+ {} | t | f | t | t | f | f | t | t
+ {"a": 1, "b": null} | t | f | t | t | f | f | t | t
+ {"a": null} | t | f | t | t | f | f | t | t
+ {"a": 1, "b": [{"a": 1}, {"a": 2}]} | t | f | t | t | f | f | t | t
+ {"a": 1, "b": [{"a": 2, "b": 0}]} | t | f | t | t | f | f | t | t
+(11 rows)
+
+-- Test IS JSON deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT '1' IS JSON AS "any", ('1' || i) IS JSON SCALAR AS "scalar", '[]' IS NOT JSON ARRAY AS "array", '{}' IS JSON OBJECT WITH UNIQUE AS "object" FROM generate_series(1, 3) i;
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------------------------------------------------------------
+ Function Scan on pg_catalog.generate_series i
+ Output: ('1'::text IS JSON), (('1'::text || (i)::text) IS JSON SCALAR), (NOT ('[]'::text IS JSON ARRAY)), ('{}'::text IS JSON OBJECT WITH UNIQUE KEYS)
+ Function Call: generate_series(1, 3)
+(3 rows)
+
+CREATE VIEW is_json_view AS
+SELECT '1' IS JSON AS "any", ('1' || i) IS JSON SCALAR AS "scalar", '[]' IS NOT JSON ARRAY AS "array", '{}' IS JSON OBJECT WITH UNIQUE AS "object" FROM generate_series(1, 3) i;
+\sv is_json_view
+CREATE OR REPLACE VIEW public.is_json_view AS
+ SELECT '1'::text IS JSON AS "any",
+ ('1'::text || i) IS JSON SCALAR AS scalar,
+ NOT '[]'::text IS JSON ARRAY AS "array",
+ '{}'::text IS JSON OBJECT WITH UNIQUE KEYS AS object
+ FROM generate_series(1, 3) i(i)
+DROP VIEW is_json_view;
diff --git a/src/test/regress/sql/sqljson.sql b/src/test/regress/sql/sqljson.sql
index fbbf6a6d6e..4fd820fd51 100644
--- a/src/test/regress/sql/sqljson.sql
+++ b/src/test/regress/sql/sqljson.sql
@@ -282,3 +282,99 @@ SELECT JSON_ARRAY(SELECT i FROM (VALUES (1), (2), (NULL), (4)) foo(i) RETURNING
\sv json_array_subquery_view
DROP VIEW json_array_subquery_view;
+
+-- IS JSON predicate
+SELECT NULL IS JSON;
+SELECT NULL IS NOT JSON;
+SELECT NULL::json IS JSON;
+SELECT NULL::jsonb IS JSON;
+SELECT NULL::text IS JSON;
+SELECT NULL::bytea IS JSON;
+SELECT NULL::int IS JSON;
+
+SELECT '' IS JSON;
+
+SELECT bytea '\x00' IS JSON;
+
+CREATE TABLE test_is_json (js text);
+
+INSERT INTO test_is_json VALUES
+ (NULL),
+ (''),
+ ('123'),
+ ('"aaa "'),
+ ('true'),
+ ('null'),
+ ('[]'),
+ ('[1, "2", {}]'),
+ ('{}'),
+ ('{ "a": 1, "b": null }'),
+ ('{ "a": 1, "a": null }'),
+ ('{ "a": 1, "b": [{ "a": 1 }, { "a": 2 }] }'),
+ ('{ "a": 1, "b": [{ "a": 1, "b": 0, "a": 2 }] }'),
+ ('aaa'),
+ ('{a:1}'),
+ ('["a",]');
+
+SELECT
+ js,
+ js IS JSON "IS JSON",
+ js IS NOT JSON "IS NOT JSON",
+ js IS JSON VALUE "IS VALUE",
+ js IS JSON OBJECT "IS OBJECT",
+ js IS JSON ARRAY "IS ARRAY",
+ js IS JSON SCALAR "IS SCALAR",
+ js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE",
+ js IS JSON WITH UNIQUE KEYS "WITH UNIQUE"
+FROM
+ test_is_json;
+
+SELECT
+ js,
+ js IS JSON "IS JSON",
+ js IS NOT JSON "IS NOT JSON",
+ js IS JSON VALUE "IS VALUE",
+ js IS JSON OBJECT "IS OBJECT",
+ js IS JSON ARRAY "IS ARRAY",
+ js IS JSON SCALAR "IS SCALAR",
+ js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE",
+ js IS JSON WITH UNIQUE KEYS "WITH UNIQUE"
+FROM
+ (SELECT js::json FROM test_is_json WHERE js IS JSON) foo(js);
+
+SELECT
+ js0,
+ js IS JSON "IS JSON",
+ js IS NOT JSON "IS NOT JSON",
+ js IS JSON VALUE "IS VALUE",
+ js IS JSON OBJECT "IS OBJECT",
+ js IS JSON ARRAY "IS ARRAY",
+ js IS JSON SCALAR "IS SCALAR",
+ js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE",
+ js IS JSON WITH UNIQUE KEYS "WITH UNIQUE"
+FROM
+ (SELECT js, js::bytea FROM test_is_json WHERE js IS JSON) foo(js0, js);
+
+SELECT
+ js,
+ js IS JSON "IS JSON",
+ js IS NOT JSON "IS NOT JSON",
+ js IS JSON VALUE "IS VALUE",
+ js IS JSON OBJECT "IS OBJECT",
+ js IS JSON ARRAY "IS ARRAY",
+ js IS JSON SCALAR "IS SCALAR",
+ js IS JSON WITHOUT UNIQUE KEYS "WITHOUT UNIQUE",
+ js IS JSON WITH UNIQUE KEYS "WITH UNIQUE"
+FROM
+ (SELECT js::jsonb FROM test_is_json WHERE js IS JSON) foo(js);
+
+-- Test IS JSON deparsing
+EXPLAIN (VERBOSE, COSTS OFF)
+SELECT '1' IS JSON AS "any", ('1' || i) IS JSON SCALAR AS "scalar", '[]' IS NOT JSON ARRAY AS "array", '{}' IS JSON OBJECT WITH UNIQUE AS "object" FROM generate_series(1, 3) i;
+
+CREATE VIEW is_json_view AS
+SELECT '1' IS JSON AS "any", ('1' || i) IS JSON SCALAR AS "scalar", '[]' IS NOT JSON ARRAY AS "array", '{}' IS JSON OBJECT WITH UNIQUE AS "object" FROM generate_series(1, 3) i;
+
+\sv is_json_view
+
+DROP VIEW is_json_view;
--
2.30.2
^ permalink raw reply [nested|flat] 4+ messages in thread
* Re: s/pg_attribute_always_inline/pg_always_inline/?
@ 2026-07-10 00:05 Tomas Vondra <[email protected]>
2026-07-10 00:14 ` Re: s/pg_attribute_always_inline/pg_always_inline/? Tom Lane <[email protected]>
0 siblings, 1 reply; 4+ messages in thread
From: Tomas Vondra @ 2026-07-10 00:05 UTC (permalink / raw)
To: Tom Lane <[email protected]>; David Rowley <[email protected]>; +Cc: Peter Geoghegan <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; pgsql-hackers
On 7/9/26 02:14, Tom Lane wrote:
> David Rowley <[email protected]> writes:
>> On Thu, 9 Jul 2026 at 09:42, Tomas Vondra <[email protected]> wrote:
>>> I guess we should just commit that, so unless someone objects soon I'll
>>> just do that. ISTM the agreement is to backpatch this too.
>
>> How about just backpatching the new macro and leaving the old one in
>> place back branches?
>
> +1. Breaking valid code in released branches is unfriendly.
>
OK, that sounds reasonable. So in the backbranches we'd have both
pg_always_inline and the original pg_attriute_always_inline.
Does "backpatching the new macro" mean we'd also adjust all existing
places to use the new macro or not? I mean, we could backpatch just the
bit in c.h. I plan to adjust the places, to minimize possible conflicts
when backpatching other stuff.
regards
--
Tomas Vondra
^ permalink raw reply [nested|flat] 4+ messages in thread
* Re: s/pg_attribute_always_inline/pg_always_inline/?
2026-07-10 00:05 Re: s/pg_attribute_always_inline/pg_always_inline/? Tomas Vondra <[email protected]>
@ 2026-07-10 00:14 ` Tom Lane <[email protected]>
2026-07-10 12:34 ` Re: s/pg_attribute_always_inline/pg_always_inline/? Tomas Vondra <[email protected]>
0 siblings, 1 reply; 4+ messages in thread
From: Tom Lane @ 2026-07-10 00:14 UTC (permalink / raw)
To: Tomas Vondra <[email protected]>; +Cc: David Rowley <[email protected]>; Peter Geoghegan <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; pgsql-hackers
Tomas Vondra <[email protected]> writes:
> Does "backpatching the new macro" mean we'd also adjust all existing
> places to use the new macro or not? I mean, we could backpatch just the
> bit in c.h. I plan to adjust the places, to minimize possible conflicts
> when backpatching other stuff.
I think it'd be sufficient to add the new macro to the old branches.
The point of back-patching at all, I think, is to save ourselves
work if we have to back-patch some new code that uses the new style.
regards, tom lane
^ permalink raw reply [nested|flat] 4+ messages in thread
* Re: s/pg_attribute_always_inline/pg_always_inline/?
2026-07-10 00:05 Re: s/pg_attribute_always_inline/pg_always_inline/? Tomas Vondra <[email protected]>
2026-07-10 00:14 ` Re: s/pg_attribute_always_inline/pg_always_inline/? Tom Lane <[email protected]>
@ 2026-07-10 12:34 ` Tomas Vondra <[email protected]>
0 siblings, 0 replies; 4+ messages in thread
From: Tomas Vondra @ 2026-07-10 12:34 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: David Rowley <[email protected]>; Peter Geoghegan <[email protected]>; Andres Freund <[email protected]>; Peter Eisentraut <[email protected]>; pgsql-hackers
On 7/10/26 02:14, Tom Lane wrote:
> Tomas Vondra <[email protected]> writes:
>> Does "backpatching the new macro" mean we'd also adjust all existing
>> places to use the new macro or not? I mean, we could backpatch just the
>> bit in c.h. I plan to adjust the places, to minimize possible conflicts
>> when backpatching other stuff.
>
> I think it'd be sufficient to add the new macro to the old branches.
> The point of back-patching at all, I think, is to save ourselves
> work if we have to back-patch some new code that uses the new style.
>
I'm not very worried about new code using the new macro name. We only
use it in ~50 places, and I don't think fixes will use it very often.
It's more likely a fix will touching some code nearby, and the
cherry-pick will fail because of the difference.
I intend to backpatch "everything", with backbranches having both macros
(as in the -19 patch).
regards
--
Tomas Vondra
Attachments:
[text/x-patch] v2-0001-Shorten-pg_attribute_always_inline-to-pg_always_i-master.patch (24.5K, ../../[email protected]/2-v2-0001-Shorten-pg_attribute_always_inline-to-pg_always_i-master.patch)
download | inline diff:
From 128d933a0d25d246c4086ce01cbcb2b596938c44 Mon Sep 17 00:00:00 2001
From: test <test>
Date: Fri, 10 Jul 2026 01:54:38 +0200
Subject: [PATCH v2] Shorten pg_attribute_always_inline to pg_always_inline
The pg_attribute_always_inline macro name is so long it forces pgindent
to format the code in strange ways. Which may incentivize patch authors
to either structure the code in strange ways (e.g. reorder prototypes),
use shorter names, etc. Neither is very desirable for code readability.
This shortens the name by removing the _attribute_ part. It also makes
it more consistent with pg_noinline, which does not have the _attribute_
part either.
Backpatched to all supported branches, to prevent conflicts when
backpatching other fixes. The backbranches however keep both the old
and new macro name, so that existing code keeps working.
Author: Andres Freund <[email protected]>
Reviewed-by: Peter Geoghegan <[email protected]>
Reviewed-by: Tomas Vondra <[email protected]>
Discussion: https://postgr.es/m/bqqdehahpoa36igpictuqyn2s2mexk3t3ehidh2ffd2slb35e5@rzgksuiszgbg
Backpatch-through: 14
---
src/backend/access/heap/heapam.c | 2 +-
src/backend/access/transam/xlog.c | 4 +-
src/backend/commands/copyfromparse.c | 32 ++++++++--------
src/backend/commands/copyto.c | 4 +-
src/backend/executor/execExprInterp.c | 54 +++++++++++++--------------
src/backend/executor/execTuples.c | 6 +--
src/backend/executor/nodeHashjoin.c | 2 +-
src/backend/executor/nodeSeqscan.c | 4 +-
src/backend/nodes/queryjumblefuncs.c | 6 +--
src/backend/storage/buffer/bufmgr.c | 24 ++++++------
src/backend/utils/adt/json.c | 2 +-
src/backend/utils/cache/catcache.c | 2 +-
src/include/c.h | 8 ++--
src/include/executor/execScan.h | 8 ++--
src/include/portability/instr_time.h | 8 ++--
15 files changed, 83 insertions(+), 83 deletions(-)
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index abfd8e8970a..4f373b86028 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -519,7 +519,7 @@ heap_setscanlimits(TableScanDesc sscan, BlockNumber startBlk, BlockNumber numBlk
* multiple times, with constant arguments for all_visible,
* check_serializable.
*/
-pg_attribute_always_inline
+pg_always_inline
static int
page_collect_tuples(HeapScanDesc scan, Snapshot snapshot,
Page page, Buffer buffer,
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index a8bbf6284a7..b431a921e4b 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -1143,9 +1143,9 @@ XLogInsertRecord(XLogRecData *rdata,
*
* NB: Testing shows that XLogInsertRecord runs faster if this code is inlined;
* however, because there are two call sites, the compiler is reluctant to
- * inline. We use pg_attribute_always_inline here to try to convince it.
+ * inline. We use pg_always_inline here to try to convince it.
*/
-static pg_attribute_always_inline void
+static pg_always_inline void
ReserveXLogInsertLocation(int size, XLogRecPtr *StartPos, XLogRecPtr *EndPos,
XLogRecPtr *PrevPtr)
{
diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c
index 65fd5a0ab4f..500810577ad 100644
--- a/src/backend/commands/copyfromparse.c
+++ b/src/backend/commands/copyfromparse.c
@@ -144,22 +144,22 @@ static const char BinarySignature[11] = "PGCOPY\n\377\r\n\0";
/* non-export function prototypes */
static bool CopyReadLine(CopyFromState cstate, bool is_csv);
-static pg_attribute_always_inline bool CopyReadLineText(CopyFromState cstate,
- bool is_csv);
+static pg_always_inline bool CopyReadLineText(CopyFromState cstate,
+ bool is_csv);
static int CopyReadAttributesText(CopyFromState cstate);
static int CopyReadAttributesCSV(CopyFromState cstate);
static Datum CopyReadBinaryAttribute(CopyFromState cstate, FmgrInfo *flinfo,
Oid typioparam, int32 typmod,
bool *isnull);
-static pg_attribute_always_inline bool CopyFromTextLikeOneRow(CopyFromState cstate,
- ExprContext *econtext,
- Datum *values,
- bool *nulls,
- bool is_csv);
-static pg_attribute_always_inline bool NextCopyFromRawFieldsInternal(CopyFromState cstate,
- char ***fields,
- int *nfields,
- bool is_csv);
+static pg_always_inline bool CopyFromTextLikeOneRow(CopyFromState cstate,
+ ExprContext *econtext,
+ Datum *values,
+ bool *nulls,
+ bool is_csv);
+static pg_always_inline bool NextCopyFromRawFieldsInternal(CopyFromState cstate,
+ char ***fields,
+ int *nfields,
+ bool is_csv);
/* Low-level communications functions */
@@ -769,11 +769,11 @@ NextCopyFromRawFields(CopyFromState cstate, char ***fields, int *nfields)
*
* NOTE: force_not_null option are not applied to the returned fields.
*
- * We use pg_attribute_always_inline to reduce function call overhead
+ * We use pg_always_inline to reduce function call overhead
* and to help compilers to optimize away the 'is_csv' condition when called
* by internal functions such as CopyFromTextLikeOneRow().
*/
-static pg_attribute_always_inline bool
+static pg_always_inline bool
NextCopyFromRawFieldsInternal(CopyFromState cstate, char ***fields, int *nfields, bool is_csv)
{
int fldct;
@@ -946,10 +946,10 @@ CopyFromCSVOneRow(CopyFromState cstate, ExprContext *econtext, Datum *values,
/*
* Workhorse for CopyFromTextOneRow() and CopyFromCSVOneRow().
*
- * We use pg_attribute_always_inline to reduce function call overhead
+ * We use pg_always_inline to reduce function call overhead
* and to help compilers to optimize away the 'is_csv' condition.
*/
-static pg_attribute_always_inline bool
+static pg_always_inline bool
CopyFromTextLikeOneRow(CopyFromState cstate, ExprContext *econtext,
Datum *values, bool *nulls, bool is_csv)
{
@@ -1463,7 +1463,7 @@ CopyReadLineTextSIMDHelper(CopyFromState cstate, bool is_csv,
/*
* CopyReadLineText - inner loop of CopyReadLine for text mode
*/
-static pg_attribute_always_inline bool
+static pg_always_inline bool
CopyReadLineText(CopyFromState cstate, bool is_csv)
{
char *copy_input_buf;
diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c
index d3adc752ae3..f9bc617ddb1 100644
--- a/src/backend/commands/copyto.c
+++ b/src/backend/commands/copyto.c
@@ -294,10 +294,10 @@ CopyToCSVOneRow(CopyToState cstate, TupleTableSlot *slot)
/*
* Workhorse for CopyToTextOneRow() and CopyToCSVOneRow().
*
- * We use pg_attribute_always_inline to reduce function call overhead
+ * We use pg_always_inline to reduce function call overhead
* and to help compilers to optimize away the 'is_csv' condition.
*/
-static pg_attribute_always_inline void
+static pg_always_inline void
CopyToTextLikeOneRow(CopyToState cstate,
TupleTableSlot *slot,
bool is_csv)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 0634af964a9..d45812c23aa 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -178,24 +178,24 @@ static Datum ExecJustHashInnerVarVirt(ExprState *state, ExprContext *econtext, b
static Datum ExecJustHashOuterVarStrict(ExprState *state, ExprContext *econtext, bool *isnull);
/* execution helper functions */
-static pg_attribute_always_inline void ExecEvalArrayCompareInternal(FunctionCallInfo fcinfo,
- ArrayType *arr,
- int16 typlen,
- bool typbyval,
- char typalign,
- bool useOr,
- Datum *result,
- bool *resultnull);
-static pg_attribute_always_inline void ExecAggPlainTransByVal(AggState *aggstate,
- AggStatePerTrans pertrans,
- AggStatePerGroup pergroup,
- ExprContext *aggcontext,
- int setno);
-static pg_attribute_always_inline void ExecAggPlainTransByRef(AggState *aggstate,
- AggStatePerTrans pertrans,
- AggStatePerGroup pergroup,
- ExprContext *aggcontext,
- int setno);
+static pg_always_inline void ExecEvalArrayCompareInternal(FunctionCallInfo fcinfo,
+ ArrayType *arr,
+ int16 typlen,
+ bool typbyval,
+ char typalign,
+ bool useOr,
+ Datum *result,
+ bool *resultnull);
+static pg_always_inline void ExecAggPlainTransByVal(AggState *aggstate,
+ AggStatePerTrans pertrans,
+ AggStatePerGroup pergroup,
+ ExprContext *aggcontext,
+ int setno);
+static pg_always_inline void ExecAggPlainTransByRef(AggState *aggstate,
+ AggStatePerTrans pertrans,
+ AggStatePerGroup pergroup,
+ ExprContext *aggcontext,
+ int setno);
static char *ExecGetJsonValueItemString(JsonbValue *item, bool *resnull);
/*
@@ -2552,7 +2552,7 @@ get_cached_rowtype(Oid type_id, int32 typmod,
*/
/* implementation of ExecJust(Inner|Outer|Scan)Var */
-static pg_attribute_always_inline Datum
+static pg_always_inline Datum
ExecJustVarImpl(ExprState *state, TupleTableSlot *slot, bool *isnull)
{
ExprEvalStep *op = &state->steps[1];
@@ -2590,7 +2590,7 @@ ExecJustScanVar(ExprState *state, ExprContext *econtext, bool *isnull)
}
/* implementation of ExecJustAssign(Inner|Outer|Scan)Var */
-static pg_attribute_always_inline Datum
+static pg_always_inline Datum
ExecJustAssignVarImpl(ExprState *state, TupleTableSlot *inslot, bool *isnull)
{
ExprEvalStep *op = &state->steps[1];
@@ -2685,7 +2685,7 @@ ExecJustConst(ExprState *state, ExprContext *econtext, bool *isnull)
}
/* implementation of ExecJust(Inner|Outer|Scan)VarVirt */
-static pg_attribute_always_inline Datum
+static pg_always_inline Datum
ExecJustVarVirtImpl(ExprState *state, TupleTableSlot *slot, bool *isnull)
{
ExprEvalStep *op = &state->steps[0];
@@ -2728,7 +2728,7 @@ ExecJustScanVarVirt(ExprState *state, ExprContext *econtext, bool *isnull)
}
/* implementation of ExecJustAssign(Inner|Outer|Scan)VarVirt */
-static pg_attribute_always_inline Datum
+static pg_always_inline Datum
ExecJustAssignVarVirtImpl(ExprState *state, TupleTableSlot *inslot, bool *isnull)
{
ExprEvalStep *op = &state->steps[0];
@@ -2807,7 +2807,7 @@ ExecJustHashInnerVarWithIV(ExprState *state, ExprContext *econtext,
}
/* implementation of ExecJustHash(Inner|Outer)Var */
-static pg_attribute_always_inline Datum
+static pg_always_inline Datum
ExecJustHashVarImpl(ExprState *state, TupleTableSlot *slot, bool *isnull)
{
ExprEvalStep *fetchop = &state->steps[0];
@@ -2845,7 +2845,7 @@ ExecJustHashInnerVar(ExprState *state, ExprContext *econtext, bool *isnull)
}
/* implementation of ExecJustHash(Inner|Outer)VarVirt */
-static pg_attribute_always_inline Datum
+static pg_always_inline Datum
ExecJustHashVarVirtImpl(ExprState *state, TupleTableSlot *slot, bool *isnull)
{
ExprEvalStep *var = &state->steps[0];
@@ -4107,7 +4107,7 @@ ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op)
* Callers must handle the strict LHS-is-NULL; return NULL fast path prior to
* calling this.
*/
-static pg_attribute_always_inline void
+static pg_always_inline void
ExecEvalArrayCompareInternal(FunctionCallInfo fcinfo, ArrayType *arr,
int16 typlen, bool typbyval, char typalign,
bool useOr, Datum *result, bool *resultnull)
@@ -5906,7 +5906,7 @@ ExecEvalAggOrderedTransTuple(ExprState *state, ExprEvalStep *op,
}
/* implementation of transition function invocation for byval types */
-static pg_attribute_always_inline void
+static pg_always_inline void
ExecAggPlainTransByVal(AggState *aggstate, AggStatePerTrans pertrans,
AggStatePerGroup pergroup,
ExprContext *aggcontext, int setno)
@@ -5938,7 +5938,7 @@ ExecAggPlainTransByVal(AggState *aggstate, AggStatePerTrans pertrans,
}
/* implementation of transition function invocation for byref types */
-static pg_attribute_always_inline void
+static pg_always_inline void
ExecAggPlainTransByRef(AggState *aggstate, AggStatePerTrans pertrans,
AggStatePerGroup pergroup,
ExprContext *aggcontext, int setno)
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 7f4ebf95432..97ae019d10a 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -72,8 +72,8 @@
static TupleDesc ExecTypeFromTLInternal(List *targetList,
bool skipjunk);
-static pg_attribute_always_inline void slot_deform_heap_tuple(TupleTableSlot *slot, HeapTuple tuple, uint32 *offp,
- int reqnatts, bool support_cstring);
+static pg_always_inline void slot_deform_heap_tuple(TupleTableSlot *slot, HeapTuple tuple, uint32 *offp,
+ int reqnatts, bool support_cstring);
static inline void tts_buffer_heap_store_tuple(TupleTableSlot *slot,
HeapTuple tuple,
Buffer buffer,
@@ -1013,7 +1013,7 @@ tts_buffer_heap_store_tuple(TupleTableSlot *slot, HeapTuple tuple,
* emit code during inlining for cstring deforming when it's required.
* cstrings can exist in MinimalTuples, but not in HeapTuples.
*/
-static pg_attribute_always_inline void
+static pg_always_inline void
slot_deform_heap_tuple(TupleTableSlot *slot, HeapTuple tuple, uint32 *offp,
int reqnatts, bool support_cstring)
{
diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c
index 0b365d5b475..202dd866251 100644
--- a/src/backend/executor/nodeHashjoin.c
+++ b/src/backend/executor/nodeHashjoin.c
@@ -221,7 +221,7 @@ static void ExecParallelHashJoinPartitionOuter(HashJoinState *hjstate);
* the other one is "outer".
* ----------------------------------------------------------------
*/
-static pg_attribute_always_inline TupleTableSlot *
+static pg_always_inline TupleTableSlot *
ExecHashJoinImpl(PlanState *pstate, bool parallel)
{
HashJoinState *node = castNode(HashJoinState, pstate);
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
index 5bcb0a861d7..b8c528ca089 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -48,7 +48,7 @@ static TupleTableSlot *SeqNext(SeqScanState *node);
* This is a workhorse for ExecSeqScan
* ----------------------------------------------------------------
*/
-static pg_attribute_always_inline TupleTableSlot *
+static pg_always_inline TupleTableSlot *
SeqNext(SeqScanState *node)
{
TableScanDesc scandesc;
@@ -95,7 +95,7 @@ SeqNext(SeqScanState *node)
/*
* SeqRecheck -- access method routine to recheck a tuple in EvalPlanQual
*/
-static pg_attribute_always_inline bool
+static pg_always_inline bool
SeqRecheck(SeqScanState *node, TupleTableSlot *slot)
{
/*
diff --git a/src/backend/nodes/queryjumblefuncs.c b/src/backend/nodes/queryjumblefuncs.c
index 7c63766a51c..2ce27b9e552 100644
--- a/src/backend/nodes/queryjumblefuncs.c
+++ b/src/backend/nodes/queryjumblefuncs.c
@@ -232,7 +232,7 @@ DoJumble(JumbleState *jstate, Node *node)
*
* Note: Callers must ensure that size > 0.
*/
-static pg_attribute_always_inline void
+static pg_always_inline void
AppendJumbleInternal(JumbleState *jstate, const unsigned char *item,
Size size)
{
@@ -308,7 +308,7 @@ AppendJumble(JumbleState *jstate, const unsigned char *value, Size size)
* AppendJumbleNull
* For jumbling NULL pointers
*/
-static pg_attribute_always_inline void
+static pg_always_inline void
AppendJumbleNull(JumbleState *jstate)
{
jstate->pending_nulls++;
@@ -375,7 +375,7 @@ AppendJumble64(JumbleState *jstate, const unsigned char *value)
*
* Note: Callers must ensure that there's at least 1 pending NULL.
*/
-static pg_attribute_always_inline void
+static pg_always_inline void
FlushPendingNulls(JumbleState *jstate)
{
Assert(jstate->pending_nulls > 0);
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 9ab282a76d1..3908529872a 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -649,10 +649,10 @@ static inline BufferDesc *BufferAlloc(SMgrRelation smgr,
static bool AsyncReadBuffers(ReadBuffersOperation *operation, int *nblocks_progress);
static void CheckReadBuffersOperation(ReadBuffersOperation *operation, bool is_complete);
-static pg_attribute_always_inline void TrackBufferHit(IOObject io_object,
- IOContext io_context,
- Relation rel, char persistence, SMgrRelation smgr,
- ForkNumber forknum, BlockNumber blocknum);
+static pg_always_inline void TrackBufferHit(IOObject io_object,
+ IOContext io_context,
+ Relation rel, char persistence, SMgrRelation smgr,
+ ForkNumber forknum, BlockNumber blocknum);
static Buffer GetVictimBuffer(BufferAccessStrategy strategy, IOContext io_context);
static void FlushUnlockedBuffer(BufferDesc *buf, SMgrRelation reln,
IOObject io_object, IOContext io_context);
@@ -1219,7 +1219,7 @@ ZeroAndLockBuffer(Buffer buffer, ReadBufferMode mode, bool already_valid)
* already present, or false if more work is required to either read it in or
* zero it.
*/
-static pg_attribute_always_inline Buffer
+static pg_always_inline Buffer
PinBufferForBlock(Relation rel,
SMgrRelation smgr,
char persistence,
@@ -1272,7 +1272,7 @@ PinBufferForBlock(Relation rel,
*
* smgr is required, rel is optional unless using P_NEW.
*/
-static pg_attribute_always_inline Buffer
+static pg_always_inline Buffer
ReadBuffer_common(Relation rel, SMgrRelation smgr, char smgr_persistence,
ForkNumber forkNum,
BlockNumber blockNum, ReadBufferMode mode,
@@ -1367,7 +1367,7 @@ ReadBuffer_common(Relation rel, SMgrRelation smgr, char smgr_persistence,
return buffer;
}
-static pg_attribute_always_inline bool
+static pg_always_inline bool
StartReadBuffersImpl(ReadBuffersOperation *operation,
Buffer *buffers,
BlockNumber blockNum,
@@ -1679,7 +1679,7 @@ CheckReadBuffersOperation(ReadBuffersOperation *operation, bool is_complete)
* We track various stats related to buffer hits. Because this is done in a
* few separate places, this helper exists for convenience.
*/
-static pg_attribute_always_inline void
+static pg_always_inline void
TrackBufferHit(IOObject io_object, IOContext io_context,
Relation rel, char persistence, SMgrRelation smgr,
ForkNumber forknum, BlockNumber blocknum)
@@ -2193,7 +2193,7 @@ AsyncReadBuffers(ReadBuffersOperation *operation, int *nblocks_progress)
*
* No locks are held either at entry or exit.
*/
-static pg_attribute_always_inline BufferDesc *
+static pg_always_inline BufferDesc *
BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
BlockNumber blockNum,
BufferAccessStrategy strategy,
@@ -8326,7 +8326,7 @@ MarkDirtyAllUnpinnedBuffers(int32 *buffers_dirtied,
* part of error handling, which in turn could lead to the buffer being
* replaced while IO is ongoing.
*/
-static pg_attribute_always_inline void
+static pg_always_inline void
buffer_stage_common(PgAioHandle *ioh, bool is_write, bool is_temp)
{
uint64 *io_data;
@@ -8570,7 +8570,7 @@ buffer_readv_encode_error(PgAioResult *result,
* Helper for AIO readv completion callbacks, supporting both shared and temp
* buffers. Gets called once for each buffer in a multi-page read.
*/
-static pg_attribute_always_inline void
+static pg_always_inline void
buffer_readv_complete_one(PgAioTargetData *td, uint8 buf_off, Buffer buffer,
uint8 flags, bool failed, bool is_temp,
bool *buffer_invalid,
@@ -8721,7 +8721,7 @@ buffer_readv_complete_one(PgAioTargetData *td, uint8 buf_off, Buffer buffer,
*
* Shared between shared and local buffers, to reduce code duplication.
*/
-static pg_attribute_always_inline PgAioResult
+static pg_always_inline PgAioResult
buffer_readv_complete(PgAioHandle *ioh, PgAioResult prior_result,
uint8 cb_data, bool is_temp)
{
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 0fee1b40d63..dccbe07cd2d 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -1528,7 +1528,7 @@ json_object_two_arg(PG_FUNCTION_ARGS)
* escape_json_char
* Inline helper function for escape_json* functions
*/
-static pg_attribute_always_inline void
+static pg_always_inline void
escape_json_char(StringInfo buf, char c)
{
switch (c)
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 6fb35dedf95..0c8955fc61a 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1091,7 +1091,7 @@ RehashCatCacheLists(CatCache *cp)
*
* Call CatalogCacheInitializeCache() if not yet done.
*/
-pg_attribute_always_inline
+pg_always_inline
static void
ConditionalCatalogCacheInitializeCache(CatCache *cache)
{
diff --git a/src/include/c.h b/src/include/c.h
index 0e4aea5d5a3..0e8053d1fe3 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -348,20 +348,20 @@ extern "C++"
#endif
/*
- * Use "pg_attribute_always_inline" in place of "inline" for functions that
+ * Use "pg_always_inline" in place of "inline" for functions that
* we wish to force inlining of, even when the compiler's heuristics would
* choose not to. But, if possible, don't force inlining in unoptimized
* debug builds.
*/
#if defined(__GNUC__) && defined(__OPTIMIZE__)
/* GCC supports always_inline via __attribute__ */
-#define pg_attribute_always_inline __attribute__((always_inline)) inline
+#define pg_always_inline __attribute__((always_inline)) inline
#elif defined(_MSC_VER)
/* MSVC has a special keyword for this */
-#define pg_attribute_always_inline __forceinline
+#define pg_always_inline __forceinline
#else
/* Otherwise, the best we can do is to say "inline" */
-#define pg_attribute_always_inline inline
+#define pg_always_inline inline
#endif
/*
diff --git a/src/include/executor/execScan.h b/src/include/executor/execScan.h
index 18b03235c3c..7f795b0b3fc 100644
--- a/src/include/executor/execScan.h
+++ b/src/include/executor/execScan.h
@@ -24,12 +24,12 @@
* This routine substitutes a test tuple if inside an EvalPlanQual recheck.
* Otherwise, it simply executes the access method's next-tuple routine.
*
- * The pg_attribute_always_inline attribute allows the compiler to inline
+ * The pg_always_inline attribute allows the compiler to inline
* this function into its caller. When EPQState is NULL, the EvalPlanQual
* logic is completely eliminated at compile time, avoiding unnecessary
* run-time checks and code for cases where EPQ is not required.
*/
-static pg_attribute_always_inline TupleTableSlot *
+static pg_always_inline TupleTableSlot *
ExecScanFetch(ScanState *node,
EPQState *epqstate,
ExecScanAccessMtd accessMtd,
@@ -145,7 +145,7 @@ ExecScanFetch(ScanState *node,
* conditions enforced by the access method.
*
* This function is an alternative to ExecScan, used when callers may omit
- * 'qual' or 'projInfo'. The pg_attribute_always_inline attribute allows the
+ * 'qual' or 'projInfo'. The pg_always_inline attribute allows the
* compiler to eliminate non-relevant branches at compile time, avoiding
* run-time checks in those cases.
*
@@ -157,7 +157,7 @@ ExecScanFetch(ScanState *node,
* positioned before the first qualifying tuple.
* ----------------------------------------------------------------
*/
-static pg_attribute_always_inline TupleTableSlot *
+static pg_always_inline TupleTableSlot *
ExecScanExtended(ScanState *node,
ExecScanAccessMtd accessMtd, /* function returning a tuple */
ExecScanRecheckMtd recheckMtd,
diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h
index 826cc202847..2b74ce91ecb 100644
--- a/src/include/portability/instr_time.h
+++ b/src/include/portability/instr_time.h
@@ -376,7 +376,7 @@ pg_rdtscp(void)
* only inlining the function partially.
* See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=124795
*/
-static pg_attribute_always_inline instr_time
+static pg_always_inline instr_time
pg_get_ticks(void)
{
if (likely(timing_tsc_enabled))
@@ -390,7 +390,7 @@ pg_get_ticks(void)
return pg_get_ticks_system();
}
-static pg_attribute_always_inline instr_time
+static pg_always_inline instr_time
pg_get_ticks_fast(void)
{
if (likely(timing_tsc_enabled))
@@ -406,13 +406,13 @@ pg_get_ticks_fast(void)
#else
-static pg_attribute_always_inline instr_time
+static pg_always_inline instr_time
pg_get_ticks(void)
{
return pg_get_ticks_system();
}
-static pg_attribute_always_inline instr_time
+static pg_always_inline instr_time
pg_get_ticks_fast(void)
{
return pg_get_ticks_system();
--
2.54.0
[text/x-patch] v2-0001-Shorten-pg_attribute_always_inline-to-pg_always_i-19.patch (24.7K, ../../[email protected]/3-v2-0001-Shorten-pg_attribute_always_inline-to-pg_always_i-19.patch)
download | inline diff:
From 3a47eb9f4be297d030a32fdc8db2002cbe858dcc Mon Sep 17 00:00:00 2001
From: test <test>
Date: Fri, 10 Jul 2026 01:54:38 +0200
Subject: [PATCH v2] Shorten pg_attribute_always_inline to pg_always_inline
The pg_attribute_always_inline macro name is so long it forces pgindent
to format the code in strange ways. Which may incentivize patch authors
to either structure the code in strange ways (e.g. reorder prototypes),
use shorter names, etc. Neither is very desirable for code readability.
This shortens the name by removing the _attribute_ part. It also makes
it more consistent with pg_noinline, which does not have the _attribute_
part either.
Backpatched to all supported branches, to prevent conflicts when
backpatching other fixes. The backbranches however keep both the old
and new macro name, so that existing code keeps working.
Author: Andres Freund <[email protected]>
Reviewed-by: Peter Geoghegan <[email protected]>
Reviewed-by: Tomas Vondra <[email protected]>
Discussion: https://postgr.es/m/bqqdehahpoa36igpictuqyn2s2mexk3t3ehidh2ffd2slb35e5@rzgksuiszgbg
Backpatch-through: 14
---
src/backend/access/heap/heapam.c | 2 +-
src/backend/access/transam/xlog.c | 4 +-
src/backend/commands/copyfromparse.c | 32 ++++++++--------
src/backend/commands/copyto.c | 4 +-
src/backend/executor/execExprInterp.c | 54 +++++++++++++--------------
src/backend/executor/execTuples.c | 6 +--
src/backend/executor/nodeHashjoin.c | 2 +-
src/backend/executor/nodeSeqscan.c | 4 +-
src/backend/nodes/queryjumblefuncs.c | 6 +--
src/backend/storage/buffer/bufmgr.c | 24 ++++++------
src/backend/utils/adt/json.c | 2 +-
src/backend/utils/cache/catcache.c | 2 +-
src/include/c.h | 9 ++++-
src/include/executor/execScan.h | 8 ++--
src/include/portability/instr_time.h | 8 ++--
15 files changed, 87 insertions(+), 80 deletions(-)
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index abfd8e8970a..4f373b86028 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -519,7 +519,7 @@ heap_setscanlimits(TableScanDesc sscan, BlockNumber startBlk, BlockNumber numBlk
* multiple times, with constant arguments for all_visible,
* check_serializable.
*/
-pg_attribute_always_inline
+pg_always_inline
static int
page_collect_tuples(HeapScanDesc scan, Snapshot snapshot,
Page page, Buffer buffer,
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index a81912b7441..254bb158565 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -1143,9 +1143,9 @@ XLogInsertRecord(XLogRecData *rdata,
*
* NB: Testing shows that XLogInsertRecord runs faster if this code is inlined;
* however, because there are two call sites, the compiler is reluctant to
- * inline. We use pg_attribute_always_inline here to try to convince it.
+ * inline. We use pg_always_inline here to try to convince it.
*/
-static pg_attribute_always_inline void
+static pg_always_inline void
ReserveXLogInsertLocation(int size, XLogRecPtr *StartPos, XLogRecPtr *EndPos,
XLogRecPtr *PrevPtr)
{
diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c
index 65fd5a0ab4f..500810577ad 100644
--- a/src/backend/commands/copyfromparse.c
+++ b/src/backend/commands/copyfromparse.c
@@ -144,22 +144,22 @@ static const char BinarySignature[11] = "PGCOPY\n\377\r\n\0";
/* non-export function prototypes */
static bool CopyReadLine(CopyFromState cstate, bool is_csv);
-static pg_attribute_always_inline bool CopyReadLineText(CopyFromState cstate,
- bool is_csv);
+static pg_always_inline bool CopyReadLineText(CopyFromState cstate,
+ bool is_csv);
static int CopyReadAttributesText(CopyFromState cstate);
static int CopyReadAttributesCSV(CopyFromState cstate);
static Datum CopyReadBinaryAttribute(CopyFromState cstate, FmgrInfo *flinfo,
Oid typioparam, int32 typmod,
bool *isnull);
-static pg_attribute_always_inline bool CopyFromTextLikeOneRow(CopyFromState cstate,
- ExprContext *econtext,
- Datum *values,
- bool *nulls,
- bool is_csv);
-static pg_attribute_always_inline bool NextCopyFromRawFieldsInternal(CopyFromState cstate,
- char ***fields,
- int *nfields,
- bool is_csv);
+static pg_always_inline bool CopyFromTextLikeOneRow(CopyFromState cstate,
+ ExprContext *econtext,
+ Datum *values,
+ bool *nulls,
+ bool is_csv);
+static pg_always_inline bool NextCopyFromRawFieldsInternal(CopyFromState cstate,
+ char ***fields,
+ int *nfields,
+ bool is_csv);
/* Low-level communications functions */
@@ -769,11 +769,11 @@ NextCopyFromRawFields(CopyFromState cstate, char ***fields, int *nfields)
*
* NOTE: force_not_null option are not applied to the returned fields.
*
- * We use pg_attribute_always_inline to reduce function call overhead
+ * We use pg_always_inline to reduce function call overhead
* and to help compilers to optimize away the 'is_csv' condition when called
* by internal functions such as CopyFromTextLikeOneRow().
*/
-static pg_attribute_always_inline bool
+static pg_always_inline bool
NextCopyFromRawFieldsInternal(CopyFromState cstate, char ***fields, int *nfields, bool is_csv)
{
int fldct;
@@ -946,10 +946,10 @@ CopyFromCSVOneRow(CopyFromState cstate, ExprContext *econtext, Datum *values,
/*
* Workhorse for CopyFromTextOneRow() and CopyFromCSVOneRow().
*
- * We use pg_attribute_always_inline to reduce function call overhead
+ * We use pg_always_inline to reduce function call overhead
* and to help compilers to optimize away the 'is_csv' condition.
*/
-static pg_attribute_always_inline bool
+static pg_always_inline bool
CopyFromTextLikeOneRow(CopyFromState cstate, ExprContext *econtext,
Datum *values, bool *nulls, bool is_csv)
{
@@ -1463,7 +1463,7 @@ CopyReadLineTextSIMDHelper(CopyFromState cstate, bool is_csv,
/*
* CopyReadLineText - inner loop of CopyReadLine for text mode
*/
-static pg_attribute_always_inline bool
+static pg_always_inline bool
CopyReadLineText(CopyFromState cstate, bool is_csv)
{
char *copy_input_buf;
diff --git a/src/backend/commands/copyto.c b/src/backend/commands/copyto.c
index d3adc752ae3..f9bc617ddb1 100644
--- a/src/backend/commands/copyto.c
+++ b/src/backend/commands/copyto.c
@@ -294,10 +294,10 @@ CopyToCSVOneRow(CopyToState cstate, TupleTableSlot *slot)
/*
* Workhorse for CopyToTextOneRow() and CopyToCSVOneRow().
*
- * We use pg_attribute_always_inline to reduce function call overhead
+ * We use pg_always_inline to reduce function call overhead
* and to help compilers to optimize away the 'is_csv' condition.
*/
-static pg_attribute_always_inline void
+static pg_always_inline void
CopyToTextLikeOneRow(CopyToState cstate,
TupleTableSlot *slot,
bool is_csv)
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 0634af964a9..d45812c23aa 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -178,24 +178,24 @@ static Datum ExecJustHashInnerVarVirt(ExprState *state, ExprContext *econtext, b
static Datum ExecJustHashOuterVarStrict(ExprState *state, ExprContext *econtext, bool *isnull);
/* execution helper functions */
-static pg_attribute_always_inline void ExecEvalArrayCompareInternal(FunctionCallInfo fcinfo,
- ArrayType *arr,
- int16 typlen,
- bool typbyval,
- char typalign,
- bool useOr,
- Datum *result,
- bool *resultnull);
-static pg_attribute_always_inline void ExecAggPlainTransByVal(AggState *aggstate,
- AggStatePerTrans pertrans,
- AggStatePerGroup pergroup,
- ExprContext *aggcontext,
- int setno);
-static pg_attribute_always_inline void ExecAggPlainTransByRef(AggState *aggstate,
- AggStatePerTrans pertrans,
- AggStatePerGroup pergroup,
- ExprContext *aggcontext,
- int setno);
+static pg_always_inline void ExecEvalArrayCompareInternal(FunctionCallInfo fcinfo,
+ ArrayType *arr,
+ int16 typlen,
+ bool typbyval,
+ char typalign,
+ bool useOr,
+ Datum *result,
+ bool *resultnull);
+static pg_always_inline void ExecAggPlainTransByVal(AggState *aggstate,
+ AggStatePerTrans pertrans,
+ AggStatePerGroup pergroup,
+ ExprContext *aggcontext,
+ int setno);
+static pg_always_inline void ExecAggPlainTransByRef(AggState *aggstate,
+ AggStatePerTrans pertrans,
+ AggStatePerGroup pergroup,
+ ExprContext *aggcontext,
+ int setno);
static char *ExecGetJsonValueItemString(JsonbValue *item, bool *resnull);
/*
@@ -2552,7 +2552,7 @@ get_cached_rowtype(Oid type_id, int32 typmod,
*/
/* implementation of ExecJust(Inner|Outer|Scan)Var */
-static pg_attribute_always_inline Datum
+static pg_always_inline Datum
ExecJustVarImpl(ExprState *state, TupleTableSlot *slot, bool *isnull)
{
ExprEvalStep *op = &state->steps[1];
@@ -2590,7 +2590,7 @@ ExecJustScanVar(ExprState *state, ExprContext *econtext, bool *isnull)
}
/* implementation of ExecJustAssign(Inner|Outer|Scan)Var */
-static pg_attribute_always_inline Datum
+static pg_always_inline Datum
ExecJustAssignVarImpl(ExprState *state, TupleTableSlot *inslot, bool *isnull)
{
ExprEvalStep *op = &state->steps[1];
@@ -2685,7 +2685,7 @@ ExecJustConst(ExprState *state, ExprContext *econtext, bool *isnull)
}
/* implementation of ExecJust(Inner|Outer|Scan)VarVirt */
-static pg_attribute_always_inline Datum
+static pg_always_inline Datum
ExecJustVarVirtImpl(ExprState *state, TupleTableSlot *slot, bool *isnull)
{
ExprEvalStep *op = &state->steps[0];
@@ -2728,7 +2728,7 @@ ExecJustScanVarVirt(ExprState *state, ExprContext *econtext, bool *isnull)
}
/* implementation of ExecJustAssign(Inner|Outer|Scan)VarVirt */
-static pg_attribute_always_inline Datum
+static pg_always_inline Datum
ExecJustAssignVarVirtImpl(ExprState *state, TupleTableSlot *inslot, bool *isnull)
{
ExprEvalStep *op = &state->steps[0];
@@ -2807,7 +2807,7 @@ ExecJustHashInnerVarWithIV(ExprState *state, ExprContext *econtext,
}
/* implementation of ExecJustHash(Inner|Outer)Var */
-static pg_attribute_always_inline Datum
+static pg_always_inline Datum
ExecJustHashVarImpl(ExprState *state, TupleTableSlot *slot, bool *isnull)
{
ExprEvalStep *fetchop = &state->steps[0];
@@ -2845,7 +2845,7 @@ ExecJustHashInnerVar(ExprState *state, ExprContext *econtext, bool *isnull)
}
/* implementation of ExecJustHash(Inner|Outer)VarVirt */
-static pg_attribute_always_inline Datum
+static pg_always_inline Datum
ExecJustHashVarVirtImpl(ExprState *state, TupleTableSlot *slot, bool *isnull)
{
ExprEvalStep *var = &state->steps[0];
@@ -4107,7 +4107,7 @@ ExecEvalScalarArrayOp(ExprState *state, ExprEvalStep *op)
* Callers must handle the strict LHS-is-NULL; return NULL fast path prior to
* calling this.
*/
-static pg_attribute_always_inline void
+static pg_always_inline void
ExecEvalArrayCompareInternal(FunctionCallInfo fcinfo, ArrayType *arr,
int16 typlen, bool typbyval, char typalign,
bool useOr, Datum *result, bool *resultnull)
@@ -5906,7 +5906,7 @@ ExecEvalAggOrderedTransTuple(ExprState *state, ExprEvalStep *op,
}
/* implementation of transition function invocation for byval types */
-static pg_attribute_always_inline void
+static pg_always_inline void
ExecAggPlainTransByVal(AggState *aggstate, AggStatePerTrans pertrans,
AggStatePerGroup pergroup,
ExprContext *aggcontext, int setno)
@@ -5938,7 +5938,7 @@ ExecAggPlainTransByVal(AggState *aggstate, AggStatePerTrans pertrans,
}
/* implementation of transition function invocation for byref types */
-static pg_attribute_always_inline void
+static pg_always_inline void
ExecAggPlainTransByRef(AggState *aggstate, AggStatePerTrans pertrans,
AggStatePerGroup pergroup,
ExprContext *aggcontext, int setno)
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 7f4ebf95432..97ae019d10a 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -72,8 +72,8 @@
static TupleDesc ExecTypeFromTLInternal(List *targetList,
bool skipjunk);
-static pg_attribute_always_inline void slot_deform_heap_tuple(TupleTableSlot *slot, HeapTuple tuple, uint32 *offp,
- int reqnatts, bool support_cstring);
+static pg_always_inline void slot_deform_heap_tuple(TupleTableSlot *slot, HeapTuple tuple, uint32 *offp,
+ int reqnatts, bool support_cstring);
static inline void tts_buffer_heap_store_tuple(TupleTableSlot *slot,
HeapTuple tuple,
Buffer buffer,
@@ -1013,7 +1013,7 @@ tts_buffer_heap_store_tuple(TupleTableSlot *slot, HeapTuple tuple,
* emit code during inlining for cstring deforming when it's required.
* cstrings can exist in MinimalTuples, but not in HeapTuples.
*/
-static pg_attribute_always_inline void
+static pg_always_inline void
slot_deform_heap_tuple(TupleTableSlot *slot, HeapTuple tuple, uint32 *offp,
int reqnatts, bool support_cstring)
{
diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c
index 0b365d5b475..202dd866251 100644
--- a/src/backend/executor/nodeHashjoin.c
+++ b/src/backend/executor/nodeHashjoin.c
@@ -221,7 +221,7 @@ static void ExecParallelHashJoinPartitionOuter(HashJoinState *hjstate);
* the other one is "outer".
* ----------------------------------------------------------------
*/
-static pg_attribute_always_inline TupleTableSlot *
+static pg_always_inline TupleTableSlot *
ExecHashJoinImpl(PlanState *pstate, bool parallel)
{
HashJoinState *node = castNode(HashJoinState, pstate);
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
index 5bcb0a861d7..b8c528ca089 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -48,7 +48,7 @@ static TupleTableSlot *SeqNext(SeqScanState *node);
* This is a workhorse for ExecSeqScan
* ----------------------------------------------------------------
*/
-static pg_attribute_always_inline TupleTableSlot *
+static pg_always_inline TupleTableSlot *
SeqNext(SeqScanState *node)
{
TableScanDesc scandesc;
@@ -95,7 +95,7 @@ SeqNext(SeqScanState *node)
/*
* SeqRecheck -- access method routine to recheck a tuple in EvalPlanQual
*/
-static pg_attribute_always_inline bool
+static pg_always_inline bool
SeqRecheck(SeqScanState *node, TupleTableSlot *slot)
{
/*
diff --git a/src/backend/nodes/queryjumblefuncs.c b/src/backend/nodes/queryjumblefuncs.c
index 7c63766a51c..2ce27b9e552 100644
--- a/src/backend/nodes/queryjumblefuncs.c
+++ b/src/backend/nodes/queryjumblefuncs.c
@@ -232,7 +232,7 @@ DoJumble(JumbleState *jstate, Node *node)
*
* Note: Callers must ensure that size > 0.
*/
-static pg_attribute_always_inline void
+static pg_always_inline void
AppendJumbleInternal(JumbleState *jstate, const unsigned char *item,
Size size)
{
@@ -308,7 +308,7 @@ AppendJumble(JumbleState *jstate, const unsigned char *value, Size size)
* AppendJumbleNull
* For jumbling NULL pointers
*/
-static pg_attribute_always_inline void
+static pg_always_inline void
AppendJumbleNull(JumbleState *jstate)
{
jstate->pending_nulls++;
@@ -375,7 +375,7 @@ AppendJumble64(JumbleState *jstate, const unsigned char *value)
*
* Note: Callers must ensure that there's at least 1 pending NULL.
*/
-static pg_attribute_always_inline void
+static pg_always_inline void
FlushPendingNulls(JumbleState *jstate)
{
Assert(jstate->pending_nulls > 0);
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 9ab282a76d1..3908529872a 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -649,10 +649,10 @@ static inline BufferDesc *BufferAlloc(SMgrRelation smgr,
static bool AsyncReadBuffers(ReadBuffersOperation *operation, int *nblocks_progress);
static void CheckReadBuffersOperation(ReadBuffersOperation *operation, bool is_complete);
-static pg_attribute_always_inline void TrackBufferHit(IOObject io_object,
- IOContext io_context,
- Relation rel, char persistence, SMgrRelation smgr,
- ForkNumber forknum, BlockNumber blocknum);
+static pg_always_inline void TrackBufferHit(IOObject io_object,
+ IOContext io_context,
+ Relation rel, char persistence, SMgrRelation smgr,
+ ForkNumber forknum, BlockNumber blocknum);
static Buffer GetVictimBuffer(BufferAccessStrategy strategy, IOContext io_context);
static void FlushUnlockedBuffer(BufferDesc *buf, SMgrRelation reln,
IOObject io_object, IOContext io_context);
@@ -1219,7 +1219,7 @@ ZeroAndLockBuffer(Buffer buffer, ReadBufferMode mode, bool already_valid)
* already present, or false if more work is required to either read it in or
* zero it.
*/
-static pg_attribute_always_inline Buffer
+static pg_always_inline Buffer
PinBufferForBlock(Relation rel,
SMgrRelation smgr,
char persistence,
@@ -1272,7 +1272,7 @@ PinBufferForBlock(Relation rel,
*
* smgr is required, rel is optional unless using P_NEW.
*/
-static pg_attribute_always_inline Buffer
+static pg_always_inline Buffer
ReadBuffer_common(Relation rel, SMgrRelation smgr, char smgr_persistence,
ForkNumber forkNum,
BlockNumber blockNum, ReadBufferMode mode,
@@ -1367,7 +1367,7 @@ ReadBuffer_common(Relation rel, SMgrRelation smgr, char smgr_persistence,
return buffer;
}
-static pg_attribute_always_inline bool
+static pg_always_inline bool
StartReadBuffersImpl(ReadBuffersOperation *operation,
Buffer *buffers,
BlockNumber blockNum,
@@ -1679,7 +1679,7 @@ CheckReadBuffersOperation(ReadBuffersOperation *operation, bool is_complete)
* We track various stats related to buffer hits. Because this is done in a
* few separate places, this helper exists for convenience.
*/
-static pg_attribute_always_inline void
+static pg_always_inline void
TrackBufferHit(IOObject io_object, IOContext io_context,
Relation rel, char persistence, SMgrRelation smgr,
ForkNumber forknum, BlockNumber blocknum)
@@ -2193,7 +2193,7 @@ AsyncReadBuffers(ReadBuffersOperation *operation, int *nblocks_progress)
*
* No locks are held either at entry or exit.
*/
-static pg_attribute_always_inline BufferDesc *
+static pg_always_inline BufferDesc *
BufferAlloc(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
BlockNumber blockNum,
BufferAccessStrategy strategy,
@@ -8326,7 +8326,7 @@ MarkDirtyAllUnpinnedBuffers(int32 *buffers_dirtied,
* part of error handling, which in turn could lead to the buffer being
* replaced while IO is ongoing.
*/
-static pg_attribute_always_inline void
+static pg_always_inline void
buffer_stage_common(PgAioHandle *ioh, bool is_write, bool is_temp)
{
uint64 *io_data;
@@ -8570,7 +8570,7 @@ buffer_readv_encode_error(PgAioResult *result,
* Helper for AIO readv completion callbacks, supporting both shared and temp
* buffers. Gets called once for each buffer in a multi-page read.
*/
-static pg_attribute_always_inline void
+static pg_always_inline void
buffer_readv_complete_one(PgAioTargetData *td, uint8 buf_off, Buffer buffer,
uint8 flags, bool failed, bool is_temp,
bool *buffer_invalid,
@@ -8721,7 +8721,7 @@ buffer_readv_complete_one(PgAioTargetData *td, uint8 buf_off, Buffer buffer,
*
* Shared between shared and local buffers, to reduce code duplication.
*/
-static pg_attribute_always_inline PgAioResult
+static pg_always_inline PgAioResult
buffer_readv_complete(PgAioHandle *ioh, PgAioResult prior_result,
uint8 cb_data, bool is_temp)
{
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 0fee1b40d63..dccbe07cd2d 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -1528,7 +1528,7 @@ json_object_two_arg(PG_FUNCTION_ARGS)
* escape_json_char
* Inline helper function for escape_json* functions
*/
-static pg_attribute_always_inline void
+static pg_always_inline void
escape_json_char(StringInfo buf, char c)
{
switch (c)
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 6fb35dedf95..0c8955fc61a 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1091,7 +1091,7 @@ RehashCatCacheLists(CatCache *cp)
*
* Call CatalogCacheInitializeCache() if not yet done.
*/
-pg_attribute_always_inline
+pg_always_inline
static void
ConditionalCatalogCacheInitializeCache(CatCache *cache)
{
diff --git a/src/include/c.h b/src/include/c.h
index f32989a6331..c641f004eff 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -289,19 +289,26 @@ extern "C++"
#endif
/*
- * Use "pg_attribute_always_inline" in place of "inline" for functions that
+ * Use "pg_always_inline" in place of "inline" for functions that
* we wish to force inlining of, even when the compiler's heuristics would
* choose not to. But, if possible, don't force inlining in unoptimized
* debug builds.
+ *
+ * XXX The "pg_attribute_always_inline" variant is kept for backwards
+ * compatibility with existing code. All new code should use the shorter
+ * variant "pg_always_inline."
*/
#if defined(__GNUC__) && defined(__OPTIMIZE__)
/* GCC supports always_inline via __attribute__ */
+#define pg_always_inline __attribute__((always_inline)) inline
#define pg_attribute_always_inline __attribute__((always_inline)) inline
#elif defined(_MSC_VER)
/* MSVC has a special keyword for this */
+#define pg_always_inline __forceinline
#define pg_attribute_always_inline __forceinline
#else
/* Otherwise, the best we can do is to say "inline" */
+#define pg_always_inline inline
#define pg_attribute_always_inline inline
#endif
diff --git a/src/include/executor/execScan.h b/src/include/executor/execScan.h
index 18b03235c3c..7f795b0b3fc 100644
--- a/src/include/executor/execScan.h
+++ b/src/include/executor/execScan.h
@@ -24,12 +24,12 @@
* This routine substitutes a test tuple if inside an EvalPlanQual recheck.
* Otherwise, it simply executes the access method's next-tuple routine.
*
- * The pg_attribute_always_inline attribute allows the compiler to inline
+ * The pg_always_inline attribute allows the compiler to inline
* this function into its caller. When EPQState is NULL, the EvalPlanQual
* logic is completely eliminated at compile time, avoiding unnecessary
* run-time checks and code for cases where EPQ is not required.
*/
-static pg_attribute_always_inline TupleTableSlot *
+static pg_always_inline TupleTableSlot *
ExecScanFetch(ScanState *node,
EPQState *epqstate,
ExecScanAccessMtd accessMtd,
@@ -145,7 +145,7 @@ ExecScanFetch(ScanState *node,
* conditions enforced by the access method.
*
* This function is an alternative to ExecScan, used when callers may omit
- * 'qual' or 'projInfo'. The pg_attribute_always_inline attribute allows the
+ * 'qual' or 'projInfo'. The pg_always_inline attribute allows the
* compiler to eliminate non-relevant branches at compile time, avoiding
* run-time checks in those cases.
*
@@ -157,7 +157,7 @@ ExecScanFetch(ScanState *node,
* positioned before the first qualifying tuple.
* ----------------------------------------------------------------
*/
-static pg_attribute_always_inline TupleTableSlot *
+static pg_always_inline TupleTableSlot *
ExecScanExtended(ScanState *node,
ExecScanAccessMtd accessMtd, /* function returning a tuple */
ExecScanRecheckMtd recheckMtd,
diff --git a/src/include/portability/instr_time.h b/src/include/portability/instr_time.h
index 655f8737b6f..650770754d5 100644
--- a/src/include/portability/instr_time.h
+++ b/src/include/portability/instr_time.h
@@ -376,7 +376,7 @@ pg_rdtscp(void)
* only inlining the function partially.
* See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=124795
*/
-static pg_attribute_always_inline instr_time
+static pg_always_inline instr_time
pg_get_ticks(void)
{
if (likely(timing_tsc_enabled))
@@ -390,7 +390,7 @@ pg_get_ticks(void)
return pg_get_ticks_system();
}
-static pg_attribute_always_inline instr_time
+static pg_always_inline instr_time
pg_get_ticks_fast(void)
{
if (likely(timing_tsc_enabled))
@@ -406,13 +406,13 @@ pg_get_ticks_fast(void)
#else
-static pg_attribute_always_inline instr_time
+static pg_always_inline instr_time
pg_get_ticks(void)
{
return pg_get_ticks_system();
}
-static pg_attribute_always_inline instr_time
+static pg_always_inline instr_time
pg_get_ticks_fast(void)
{
return pg_get_ticks_system();
--
2.54.0
^ permalink raw reply [nested|flat] 4+ messages in thread
end of thread, other threads:[~2026-07-10 12:34 UTC | newest]
Thread overview: 4+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2023-03-31 16:49 Re: SQL/JSON revisited Alvaro Herrera <[email protected]>
2026-07-10 00:05 Re: s/pg_attribute_always_inline/pg_always_inline/? Tomas Vondra <[email protected]>
2026-07-10 00:14 ` Re: s/pg_attribute_always_inline/pg_always_inline/? Tom Lane <[email protected]>
2026-07-10 12:34 ` Re: s/pg_attribute_always_inline/pg_always_inline/? Tomas Vondra <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox