Fix error reporting for SQL/JSON path type mismatches
authorAmit Langote <amitlan@postgresql.org>
Thu, 27 Nov 2025 01:43:29 +0000 (10:43 +0900)
committerAmit Langote <amitlan@postgresql.org>
Thu, 27 Nov 2025 03:07:01 +0000 (12:07 +0900)
transformJsonFuncExpr() used exprType()/exprLocation() on the
possibly coerced path expression, which could be NULL when
coercion to jsonpath failed, leading to "cache lookup failed
for type 0" errors.

Preserve the original expression node so that type and location
in the "must be of type jsonpath" error are reported correctly.
Add regression tests to cover these cases.

Reported-by: Jian He <jian.universality@gmail.com>
Author: Jian He <jian.universality@gmail.com>
Reviewed-by: Kirill Reshke <reshkekirill@gmail.com>
Discussion: https://postgr.es/m/CACJufxHunVg81JMuNo8Yvv_hJD0DicgaVN2Wteu8aJbVJPBjZA@mail.gmail.com
Backpatch-through: 17

src/backend/parser/parse_expr.c
src/test/regress/expected/sqljson_queryfuncs.out
src/test/regress/sql/sqljson_queryfuncs.sql

index 32d6ae918caa995b9f6ea8280c30cc934dd9ac14..44fd1385f8c559ecc66b2a63a0435302cee8373a 100644 (file)
@@ -4285,6 +4285,9 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
 {
    JsonExpr   *jsexpr;
    Node       *path_spec;
+   Oid         pathspec_type;
+   int         pathspec_loc;
+   Node       *coerced_path_spec;
    const char *func_name = NULL;
    JsonFormatType default_format;
 
@@ -4500,17 +4503,21 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func)
    jsexpr->format = func->context_item->format;
 
    path_spec = transformExprRecurse(pstate, func->pathspec);
-   path_spec = coerce_to_target_type(pstate, path_spec, exprType(path_spec),
-                                     JSONPATHOID, -1,
-                                     COERCION_EXPLICIT, COERCE_IMPLICIT_CAST,
-                                     exprLocation(path_spec));
-   if (path_spec == NULL)
+   pathspec_type = exprType(path_spec);
+   pathspec_loc = exprLocation(path_spec);
+   coerced_path_spec = coerce_to_target_type(pstate, path_spec,
+                                             pathspec_type,
+                                             JSONPATHOID, -1,
+                                             COERCION_EXPLICIT,
+                                             COERCE_IMPLICIT_CAST,
+                                             pathspec_loc);
+   if (coerced_path_spec == NULL)
        ereport(ERROR,
                (errcode(ERRCODE_DATATYPE_MISMATCH),
                 errmsg("JSON path expression must be of type %s, not of type %s",
-                       "jsonpath", format_type_be(exprType(path_spec))),
-                parser_errposition(pstate, exprLocation(path_spec))));
-   jsexpr->path_spec = path_spec;
+                       "jsonpath", format_type_be(pathspec_type)),
+                parser_errposition(pstate, pathspec_loc)));
+   jsexpr->path_spec = coerced_path_spec;
 
    /* Transform and coerce the PASSING arguments to jsonb. */
    transformJsonPassingArgs(pstate, func_name,
index 5a35aeb7bba3afd01a70c728481712299856e483..53145f50f186810a6b02c09ebc58d60c4bdbd640 100644 (file)
@@ -1331,6 +1331,10 @@ SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER);
  [123]
 (1 row)
 
+SELECT JSON_QUERY(jsonb '{"a": 123}', ('$' || '.' || 'a' || NULL)::date WITH WRAPPER);
+ERROR:  JSON path expression must be of type jsonpath, not of type date
+LINE 1: SELECT JSON_QUERY(jsonb '{"a": 123}', ('$' || '.' || 'a' || ...
+                                               ^
 -- Should fail (invalid path)
 SELECT JSON_QUERY(jsonb '{"a": 123}', 'error' || ' ' || 'error');
 ERROR:  syntax error at or near " " of jsonpath input
@@ -1355,6 +1359,10 @@ SELECT json_value('"aaa"', path RETURNING json) FROM jsonpaths;
  "aaa"
 (1 row)
 
+SELECT json_value('"aaa"', jsonpaths RETURNING json) FROM jsonpaths;
+ERROR:  JSON path expression must be of type jsonpath, not of type jsonpaths
+LINE 1: SELECT json_value('"aaa"', jsonpaths RETURNING json) FROM js...
+                                   ^
 -- Test PASSING argument parsing
 SELECT JSON_QUERY(jsonb 'null', '$xyz' PASSING 1 AS xy);
 ERROR:  could not find jsonpath variable "xyz"
index 8d7b225b612174bdf4290f2f7a005d206c5adbd9..a5d5e256d7ff0c16f1af3f70c984889c2eb1d8a8 100644 (file)
@@ -450,6 +450,7 @@ SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'a');
 SELECT JSON_VALUE(jsonb '{"a": 123}', '$' || '.' || 'b' DEFAULT 'foo' ON EMPTY);
 SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a');
 SELECT JSON_QUERY(jsonb '{"a": 123}', '$' || '.' || 'a' WITH WRAPPER);
+SELECT JSON_QUERY(jsonb '{"a": 123}', ('$' || '.' || 'a' || NULL)::date WITH WRAPPER);
 -- Should fail (invalid path)
 SELECT JSON_QUERY(jsonb '{"a": 123}', 'error' || ' ' || 'error');
 
@@ -460,6 +461,7 @@ SELECT JSON_QUERY(NULL FORMAT JSON, '$');
 -- Test non-const jsonpath
 CREATE TEMP TABLE jsonpaths (path) AS SELECT '$';
 SELECT json_value('"aaa"', path RETURNING json) FROM jsonpaths;
+SELECT json_value('"aaa"', jsonpaths RETURNING json) FROM jsonpaths;
 
 -- Test PASSING argument parsing
 SELECT JSON_QUERY(jsonb 'null', '$xyz' PASSING 1 AS xy);