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);

Reply via email to