Hi,
The attached patch addresses the SQL NULL-key part of this report. It was
prepared with AI assistance, including the local build and test execution.
In json_build_object_worker(), the NULL-key check currently follows the ABSENT
ON NULL skip. Moving that check before the skip makes JSON_OBJECT(NULL: NULL
ABSENT ON NULL) reject the NULL key, as the RETURNING jsonb path already does.
The existing error messages and SQLSTATEs are preserved.
The patch adds regression coverage for NULL keys with ABSENT ON NULL, for both
json and jsonb output, with and without WITH UNIQUE KEYS. It does not address
the separate array, composite, or JSON-key validation cases in this report.
The patch was built and tested against PostgreSQL master commit
9e7d152488f197e96f171f0714459c227c3f7693. With only the new regression tests
applied, sqljson failed at the new NULL-key/ABSENT/json case. With the code
change applied, sqljson and all nine JSON-family regression tests passed.
Existing coverage for legitimate NULL-value omission and duplicate keys with
omitted values continued to pass. git diff --check also passed.
Those are the results from the earlier local test run, not a new test run
today. The environment was Linux x86_64, GCC 14.2.0, with --enable-cassert
--without-readline --without-zlib --without-icu and CFLAGS='-O0 -g0'. This was
focused regression testing, not the full PostgreSQL test suite or
cross-platform validation.
Before sending, I rechecked that master still points to the same commit and
verified that the attached patch passes git apply --check against freshly
retrieved source files at that commit.
Comments on this narrowly scoped fix are welcome.
diff --git a/src/backend/utils/adt/json.c b/src/backend/utils/adt/json.c
index 28e5f3c..b58b7ce 100644
--- a/src/backend/utils/adt/json.c
+++ b/src/backend/utils/adt/json.c
@@ -1220,6 +1220,12 @@ json_build_object_worker(int nargs, const Datum *args, const bool *nulls, const
bool skip;
int key_offset;
+ /* Keys must not be null, even if their values are skipped. */
+ if (nulls[i])
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("null value not allowed for object key")));
+
/* Skip null values if absent_on_null */
skip = absent_on_null && nulls[i + 1];
@@ -1239,11 +1245,6 @@ json_build_object_worker(int nargs, const Datum *args, const bool *nulls, const
}
/* process key */
- if (nulls[i])
- ereport(ERROR,
- (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
- errmsg("null value not allowed for object key")));
-
/* save key offset before appending it */
key_offset = out->len;
diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out
index 30f9e10..0881e18 100644
--- a/src/test/regress/expected/sqljson.out
+++ b/src/test/regress/expected/sqljson.out
@@ -397,6 +397,15 @@ LINE 1: SELECT JSON_OBJECT('foo': NULL::jsonb FORMAT JSON ENCODING U...
^
SELECT JSON_OBJECT(NULL: 1);
ERROR: null value not allowed for object key
+-- NULL keys are invalid even if their values would be omitted.
+SELECT JSON_OBJECT(NULL: NULL ABSENT ON NULL);
+ERROR: null value not allowed for object key
+SELECT JSON_OBJECT(NULL: NULL ABSENT ON NULL RETURNING jsonb);
+ERROR: argument 1: key must not be null
+SELECT JSON_OBJECT(NULL: NULL ABSENT ON NULL WITH UNIQUE KEYS);
+ERROR: null value not allowed for object key
+SELECT JSON_OBJECT(NULL: NULL ABSENT ON NULL WITH UNIQUE KEYS RETURNING jsonb);
+ERROR: argument 1: key must not be null
SELECT JSON_OBJECT('a': 2 + 3);
json_object
-------------
diff --git a/src/test/regress/sql/sqljson.sql b/src/test/regress/sql/sqljson.sql
index 9fa203c..6dc52ed 100644
--- a/src/test/regress/sql/sqljson.sql
+++ b/src/test/regress/sql/sqljson.sql
@@ -86,6 +86,11 @@ SELECT JSON_OBJECT('foo': NULL::jsonb FORMAT JSON);
SELECT JSON_OBJECT('foo': NULL::jsonb FORMAT JSON ENCODING UTF8);
SELECT JSON_OBJECT(NULL: 1);
+-- NULL keys are invalid even if their values would be omitted.
+SELECT JSON_OBJECT(NULL: NULL ABSENT ON NULL);
+SELECT JSON_OBJECT(NULL: NULL ABSENT ON NULL RETURNING jsonb);
+SELECT JSON_OBJECT(NULL: NULL ABSENT ON NULL WITH UNIQUE KEYS);
+SELECT JSON_OBJECT(NULL: NULL ABSENT ON NULL WITH UNIQUE KEYS RETURNING jsonb);
SELECT JSON_OBJECT('a': 2 + 3);
SELECT JSON_OBJECT('a' VALUE 2 + 3);
--SELECT JSON_OBJECT(KEY 'a' VALUE 2 + 3);