hi.
it would be better to make all ereport in gram.y print out the
relative location.
one benefit is quickly locating the error.
in insertSelectOptions, some ereport won't call parser_errposition.
To handle that
I added a location to struct SelectLimit, so we can catch the location
in the LIMIT/FETCH/OFFSET clause.
in insertSelectOptions the error is
errmsg("WITH TIES cannot be specified without
ORDER BY clause"),
errmsg("%s and %s options cannot be used
together","SKIP LOCKED", "WITH TIES"),
to capture the exact location, i use the following way:
| FETCH first_or_next select_fetch_first_value
row_or_rows WITH TIES
@@ -13222,6 +13247,7 @@ limit_clause:
n->limitOffset = NULL;
n->limitCount = $3;
n->limitOption = LIMIT_OPTION_WITH_TIES;
+ n->location = @5;
$$ = n;
but normally we do `n->location = @1;`
not sure my change in insertSelectOptions is correct.
For other places in gram.y, I guess my change is sane.
i manually checked, after my patch, all ereport function calls in
gram.y will print out the relative location.
From c8f95f5b681b6871e76b25bc2619ad9cbcc3f688 Mon Sep 17 00:00:00 2001
From: jian he <[email protected]>
Date: Sat, 26 Oct 2024 23:04:22 +0800
Subject: [PATCH v1 1/1] add cursor position in various ereport function in
gram.y
inspired by this[1],
[1]: https://www.postgresql.org/message-id/3482022.1728839631%40sss.pgh.pa.us
but i am not so sure the handing in insertSelectOptions is correct.
discussion:
---
src/backend/parser/gram.y | 90 +++++++++++--------
src/include/nodes/parsenodes.h | 1 +
.../regress/expected/create_function_sql.out | 11 +++
src/test/regress/expected/create_table.out | 2 +
src/test/regress/expected/foreign_key.out | 4 +
src/test/regress/expected/limit.out | 4 +
src/test/regress/expected/sqljson.out | 4 +
src/test/regress/sql/create_function_sql.sql | 7 ++
src/test/regress/sql/foreign_key.sql | 1 +
9 files changed, 86 insertions(+), 38 deletions(-)
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index baca4059d2..33f4cdb0b0 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -115,6 +115,7 @@ typedef struct SelectLimit
Node *limitOffset;
Node *limitCount;
LimitOption limitOption;
+ ParseLoc location;
} SelectLimit;
/* Private struct for the result of group_clause production */
@@ -188,7 +189,7 @@ static Node *makeSQLValueFunction(SQLValueFunctionOp op, int32 typmod,
int location);
static Node *makeXmlExpr(XmlExprOp op, char *name, List *named_args,
List *args, int location);
-static List *mergeTableFuncParameters(List *func_args, List *columns);
+static List *mergeTableFuncParameters(List *func_args, List *columns, core_yyscan_t yyscanner);
static TypeName *TableFuncTypeName(List *columns);
static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_t yyscanner);
static RangeVar *makeRangeVarFromQualifiedName(char *name, List *namelist, int location,
@@ -199,7 +200,6 @@ static void SplitColQualList(List *qualList,
static void processCASbits(int cas_bits, int location, const char *constrType,
bool *deferrable, bool *initdeferred, bool *not_valid,
bool *no_inherit, core_yyscan_t yyscanner);
-static PartitionStrategy parsePartitionStrategy(char *strategy);
static void preprocess_pubobj_list(List *pubobjspec_list,
core_yyscan_t yyscanner);
static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
@@ -3149,11 +3149,14 @@ PartitionBoundSpec:
if (n->modulus == -1)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("modulus for hash partition must be specified")));
+ errmsg("modulus for hash partition must be specified"),
+ parser_errposition(@4)));
+
if (n->remainder == -1)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("remainder for hash partition must be specified")));
+ errmsg("remainder for hash partition must be specified"),
+ parser_errposition(@4)));
n->location = @3;
@@ -4532,7 +4535,18 @@ PartitionSpec: PARTITION BY ColId '(' part_params ')'
{
PartitionSpec *n = makeNode(PartitionSpec);
- n->strategy = parsePartitionStrategy($3);
+ if (!pg_strcasecmp($3, "list"))
+ n->strategy = PARTITION_STRATEGY_LIST;
+ else if (!pg_strcasecmp($3, "range"))
+ n->strategy = PARTITION_STRATEGY_RANGE;
+ else if (!pg_strcasecmp($3, "hash"))
+ n->strategy = PARTITION_STRATEGY_HASH;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unrecognized partitioning strategy \"%s\"", $3),
+ parser_errposition(@3)));
+
n->partParams = $5;
n->location = @1;
@@ -5966,7 +5980,8 @@ CreateTrigStmt:
if (n->replace) /* not supported, see CreateTrigger */
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE OR REPLACE CONSTRAINT TRIGGER is not supported")));
+ errmsg("CREATE OR REPLACE CONSTRAINT TRIGGER is not supported"),
+ parser_errposition(@2)));
n->isconstraint = true;
n->trigname = $5;
n->relation = $9;
@@ -6251,7 +6266,8 @@ CreateAssertionStmt:
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE ASSERTION is not yet implemented")));
+ errmsg("CREATE ASSERTION is not yet implemented"),
+ parser_errposition(@2)));
$$ = NULL;
}
@@ -8294,7 +8310,7 @@ CreateFunctionStmt:
n->is_procedure = false;
n->replace = $2;
n->funcname = $4;
- n->parameters = mergeTableFuncParameters($5, $9);
+ n->parameters = mergeTableFuncParameters($5, $9, yyscanner);
n->returnType = TableFuncTypeName($9);
n->returnType->location = @7;
n->options = $11;
@@ -8427,6 +8443,7 @@ func_arg:
n->argType = $3;
n->mode = $1;
n->defexpr = NULL;
+ n->location = @1;
$$ = n;
}
| param_name arg_class func_type
@@ -8437,6 +8454,7 @@ func_arg:
n->argType = $3;
n->mode = $2;
n->defexpr = NULL;
+ n->location = @1;
$$ = n;
}
| param_name func_type
@@ -8447,6 +8465,7 @@ func_arg:
n->argType = $2;
n->mode = FUNC_PARAM_DEFAULT;
n->defexpr = NULL;
+ n->location = @1;
$$ = n;
}
| arg_class func_type
@@ -8457,6 +8476,7 @@ func_arg:
n->argType = $2;
n->mode = $1;
n->defexpr = NULL;
+ n->location = @1;
$$ = n;
}
| func_type
@@ -8467,6 +8487,7 @@ func_arg:
n->argType = $1;
n->mode = FUNC_PARAM_DEFAULT;
n->defexpr = NULL;
+ n->location = @1;
$$ = n;
}
;
@@ -8803,6 +8824,7 @@ table_func_column: param_name func_type
n->argType = $2;
n->mode = FUNC_PARAM_TABLE;
n->defexpr = NULL;
+ n->location = @1;
$$ = n;
}
;
@@ -13171,6 +13193,7 @@ select_limit:
n->limitOffset = $1;
n->limitCount = NULL;
n->limitOption = LIMIT_OPTION_COUNT;
+ n->location = @1;
$$ = n;
}
;
@@ -13188,6 +13211,7 @@ limit_clause:
n->limitOffset = NULL;
n->limitCount = $2;
n->limitOption = LIMIT_OPTION_COUNT;
+ n->location = @1;
$$ = n;
}
| LIMIT select_limit_value ',' select_offset_value
@@ -13213,6 +13237,7 @@ limit_clause:
n->limitOffset = NULL;
n->limitCount = $3;
n->limitOption = LIMIT_OPTION_COUNT;
+ n->location = @1;
$$ = n;
}
| FETCH first_or_next select_fetch_first_value row_or_rows WITH TIES
@@ -13222,6 +13247,7 @@ limit_clause:
n->limitOffset = NULL;
n->limitCount = $3;
n->limitOption = LIMIT_OPTION_WITH_TIES;
+ n->location = @5;
$$ = n;
}
| FETCH first_or_next row_or_rows ONLY
@@ -13231,6 +13257,7 @@ limit_clause:
n->limitOffset = NULL;
n->limitCount = makeIntConst(1, -1);
n->limitOption = LIMIT_OPTION_COUNT;
+ n->location = @1;
$$ = n;
}
| FETCH first_or_next row_or_rows WITH TIES
@@ -13240,6 +13267,7 @@ limit_clause:
n->limitOffset = NULL;
n->limitCount = makeIntConst(1, -1);
n->limitOption = LIMIT_OPTION_WITH_TIES;
+ n->location = @1;
$$ = n;
}
;
@@ -16952,8 +16980,9 @@ json_format_clause:
encoding = JS_ENC_UTF32;
else
ereport(ERROR,
- errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("unrecognized JSON encoding: %s", $4));
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unrecognized JSON encoding: %s", $4),
+ parser_errposition(@4)));
$$ = (Node *) makeJsonFormat(JS_FORMAT_JSON, encoding, @1);
}
@@ -17455,7 +17484,8 @@ PLpgSQL_Expr: opt_distinct_clause opt_target_list
$9->limitOption == LIMIT_OPTION_WITH_TIES)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("WITH TIES cannot be specified without ORDER BY clause")));
+ errmsg("WITH TIES cannot be specified without ORDER BY clause"),
+ parser_errposition(@9)));
n->limitOption = $9->limitOption;
}
n->lockingClause = $10;
@@ -18977,11 +19007,15 @@ insertSelectOptions(SelectStmt *stmt,
if (stmt->limitOption)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("multiple limit options not allowed")));
+ errmsg("multiple limit options not allowed"),
+ parser_errposition(limitClause->location)));
+
if (!stmt->sortClause && limitClause->limitOption == LIMIT_OPTION_WITH_TIES)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("WITH TIES cannot be specified without ORDER BY clause")));
+ errmsg("WITH TIES cannot be specified without ORDER BY clause"),
+ parser_errposition(limitClause->location)));
+
if (limitClause->limitOption == LIMIT_OPTION_WITH_TIES && stmt->lockingClause)
{
ListCell *lc;
@@ -18994,7 +19028,8 @@ insertSelectOptions(SelectStmt *stmt,
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("%s and %s options cannot be used together",
- "SKIP LOCKED", "WITH TIES")));
+ "SKIP LOCKED", "WITH TIES"),
+ parser_errposition(limitClause->location)));
}
}
stmt->limitOption = limitClause->limitOption;
@@ -19187,7 +19222,7 @@ makeXmlExpr(XmlExprOp op, char *name, List *named_args, List *args,
* Merge the input and output parameters of a table function.
*/
static List *
-mergeTableFuncParameters(List *func_args, List *columns)
+mergeTableFuncParameters(List *func_args, List *columns, core_yyscan_t yyscanner)
{
ListCell *lc;
@@ -19201,7 +19236,8 @@ mergeTableFuncParameters(List *func_args, List *columns)
p->mode != FUNC_PARAM_VARIADIC)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("OUT and INOUT arguments aren't allowed in TABLE functions")));
+ errmsg("OUT and INOUT arguments aren't allowed in TABLE functions"),
+ parser_errposition(p->location)));
}
return list_concat(func_args, columns);
@@ -19417,28 +19453,6 @@ processCASbits(int cas_bits, int location, const char *constrType,
}
}
-/*
- * Parse a user-supplied partition strategy string into parse node
- * PartitionStrategy representation, or die trying.
- */
-static PartitionStrategy
-parsePartitionStrategy(char *strategy)
-{
- if (pg_strcasecmp(strategy, "list") == 0)
- return PARTITION_STRATEGY_LIST;
- else if (pg_strcasecmp(strategy, "range") == 0)
- return PARTITION_STRATEGY_RANGE;
- else if (pg_strcasecmp(strategy, "hash") == 0)
- return PARTITION_STRATEGY_HASH;
-
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("unrecognized partitioning strategy \"%s\"",
- strategy)));
- return PARTITION_STRATEGY_LIST; /* keep compiler quiet */
-
-}
-
/*
* Process pubobjspec_list to check for errors in any of the objects and
* convert PUBLICATIONOBJ_CONTINUATION into appropriate PublicationObjSpecType.
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index b40b661ec8..0d96db5638 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -3482,6 +3482,7 @@ typedef struct FunctionParameter
TypeName *argType; /* TypeName for parameter type */
FunctionParameterMode mode; /* IN/OUT/etc */
Node *defexpr; /* raw default expr, or NULL if not given */
+ ParseLoc location; /* token location, or -1 if unknown */
} FunctionParameter;
typedef struct AlterFunctionStmt
diff --git a/src/test/regress/expected/create_function_sql.out b/src/test/regress/expected/create_function_sql.out
index 50aca5940f..a44986afa3 100644
--- a/src/test/regress/expected/create_function_sql.out
+++ b/src/test/regress/expected/create_function_sql.out
@@ -49,6 +49,17 @@ SELECT functest_A_3();
f
(1 row)
+-- OUT and INOUT arguments aren't allowed in TABLE functions
+CREATE FUNCTION fn_intout(out c int)
+RETURNS TABLE(a int) LANGUAGE sql AS $BODY$ select 1 $BODY$;
+ERROR: OUT and INOUT arguments aren't allowed in TABLE functions
+LINE 1: CREATE FUNCTION fn_intout(out c int)
+ ^
+CREATE FUNCTION fn_intout(inout c int)
+RETURNS TABLE(a int) LANGUAGE sql AS $BODY$ select 1 $BODY$;
+ERROR: OUT and INOUT arguments aren't allowed in TABLE functions
+LINE 1: CREATE FUNCTION fn_intout(inout c int)
+ ^
--
-- IMMUTABLE | STABLE | VOLATILE
--
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index c45e02d42f..57a24050ab 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -198,6 +198,8 @@ CREATE TABLE partitioned (
a int
) PARTITION BY MAGIC (a);
ERROR: unrecognized partitioning strategy "magic"
+LINE 3: ) PARTITION BY MAGIC (a);
+ ^
-- specified column must be present in the table
CREATE TABLE partitioned (
a int
diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out
index b73e7dced8..ed16dea924 100644
--- a/src/test/regress/expected/foreign_key.out
+++ b/src/test/regress/expected/foreign_key.out
@@ -2916,6 +2916,10 @@ $$;
CREATE TRIGGER trig_upd_pk AFTER UPDATE ON fkpart11.pk FOR EACH ROW EXECUTE FUNCTION fkpart11.print_row();
CREATE TRIGGER trig_del_pk AFTER DELETE ON fkpart11.pk FOR EACH ROW EXECUTE FUNCTION fkpart11.print_row();
CREATE TRIGGER trig_ins_pk AFTER INSERT ON fkpart11.pk FOR EACH ROW EXECUTE FUNCTION fkpart11.print_row();
+CREATE OR REPLACE CONSTRAINT TRIGGER trig_upd_fk_parted AFTER UPDATE ON fkpart11.fk_parted INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION fkpart11.print_row();
+ERROR: CREATE OR REPLACE CONSTRAINT TRIGGER is not supported
+LINE 1: CREATE OR REPLACE CONSTRAINT TRIGGER trig_upd_fk_parted AFTE...
+ ^
CREATE CONSTRAINT TRIGGER trig_upd_fk_parted AFTER UPDATE ON fkpart11.fk_parted INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION fkpart11.print_row();
CREATE CONSTRAINT TRIGGER trig_del_fk_parted AFTER DELETE ON fkpart11.fk_parted INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION fkpart11.print_row();
CREATE CONSTRAINT TRIGGER trig_ins_fk_parted AFTER INSERT ON fkpart11.fk_parted INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION fkpart11.print_row();
diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out
index a2cd0f9f5b..f4267c002d 100644
--- a/src/test/regress/expected/limit.out
+++ b/src/test/regress/expected/limit.out
@@ -624,11 +624,15 @@ SELECT thousand
FROM onek WHERE thousand < 5
ORDER BY thousand FETCH FIRST 1 ROW WITH TIES FOR UPDATE SKIP LOCKED;
ERROR: SKIP LOCKED and WITH TIES options cannot be used together
+LINE 3: ORDER BY thousand FETCH FIRST 1 ROW WITH TIES FOR UPDATE S...
+ ^
-- should fail
SELECT ''::text AS two, unique1, unique2, stringu1
FROM onek WHERE unique1 > 50
FETCH FIRST 2 ROW WITH TIES;
ERROR: WITH TIES cannot be specified without ORDER BY clause
+LINE 3: FETCH FIRST 2 ROW WITH TIES;
+ ^
-- test ruleutils
CREATE VIEW limit_thousand_v_1 AS SELECT thousand FROM onek WHERE thousand < 995
ORDER BY thousand FETCH FIRST 5 ROWS WITH TIES OFFSET 10;
diff --git a/src/test/regress/expected/sqljson.out b/src/test/regress/expected/sqljson.out
index 70721c9a5a..435d61dd4a 100644
--- a/src/test/regress/expected/sqljson.out
+++ b/src/test/regress/expected/sqljson.out
@@ -337,6 +337,8 @@ LINE 1: SELECT JSON_OBJECT(RETURNING text FORMAT JSON ENCODING UTF8)...
^
SELECT JSON_OBJECT(RETURNING text FORMAT JSON ENCODING INVALID_ENCODING);
ERROR: unrecognized JSON encoding: invalid_encoding
+LINE 1: ...T JSON_OBJECT(RETURNING text FORMAT JSON ENCODING INVALID_EN...
+ ^
SELECT JSON_OBJECT(RETURNING bytea);
json_object
-------------
@@ -620,6 +622,8 @@ LINE 1: SELECT JSON_ARRAY(RETURNING text FORMAT JSON ENCODING UTF8);
^
SELECT JSON_ARRAY(RETURNING text FORMAT JSON ENCODING INVALID_ENCODING);
ERROR: unrecognized JSON encoding: invalid_encoding
+LINE 1: ...CT JSON_ARRAY(RETURNING text FORMAT JSON ENCODING INVALID_EN...
+ ^
SELECT JSON_ARRAY(RETURNING bytea);
json_array
------------
diff --git a/src/test/regress/sql/create_function_sql.sql b/src/test/regress/sql/create_function_sql.sql
index 89e9af3a49..384a7c0c3a 100644
--- a/src/test/regress/sql/create_function_sql.sql
+++ b/src/test/regress/sql/create_function_sql.sql
@@ -35,6 +35,13 @@ SELECT functest_A_1('abcd', '2020-01-01');
SELECT functest_A_2(ARRAY['1', '2', '3']);
SELECT functest_A_3();
+-- OUT and INOUT arguments aren't allowed in TABLE functions
+CREATE FUNCTION fn_intout(out c int)
+RETURNS TABLE(a int) LANGUAGE sql AS $BODY$ select 1 $BODY$;
+
+CREATE FUNCTION fn_intout(inout c int)
+RETURNS TABLE(a int) LANGUAGE sql AS $BODY$ select 1 $BODY$;
+
--
-- IMMUTABLE | STABLE | VOLATILE
--
diff --git a/src/test/regress/sql/foreign_key.sql b/src/test/regress/sql/foreign_key.sql
index 9b2a6b6bff..393e8524f8 100644
--- a/src/test/regress/sql/foreign_key.sql
+++ b/src/test/regress/sql/foreign_key.sql
@@ -2079,6 +2079,7 @@ $$;
CREATE TRIGGER trig_upd_pk AFTER UPDATE ON fkpart11.pk FOR EACH ROW EXECUTE FUNCTION fkpart11.print_row();
CREATE TRIGGER trig_del_pk AFTER DELETE ON fkpart11.pk FOR EACH ROW EXECUTE FUNCTION fkpart11.print_row();
CREATE TRIGGER trig_ins_pk AFTER INSERT ON fkpart11.pk FOR EACH ROW EXECUTE FUNCTION fkpart11.print_row();
+CREATE OR REPLACE CONSTRAINT TRIGGER trig_upd_fk_parted AFTER UPDATE ON fkpart11.fk_parted INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION fkpart11.print_row();
CREATE CONSTRAINT TRIGGER trig_upd_fk_parted AFTER UPDATE ON fkpart11.fk_parted INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION fkpart11.print_row();
CREATE CONSTRAINT TRIGGER trig_del_fk_parted AFTER DELETE ON fkpart11.fk_parted INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION fkpart11.print_row();
CREATE CONSTRAINT TRIGGER trig_ins_fk_parted AFTER INSERT ON fkpart11.fk_parted INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION fkpart11.print_row();
--
2.34.1