Skip to content

Commit af78205

Browse files
committed
Simplify WITH clause parser
Merge the two nearly identical parse functions into one shared helper that takes a flag for whether to parse option values. Replace the manual error catching around value parsing with the safe input function call, which is available now that PostgreSQL 16 is the minimum supported version.
1 parent 6999865 commit af78205

2 files changed

Lines changed: 36 additions & 95 deletions

File tree

src/with_clause/with_clause_parser.c

Lines changed: 35 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <access/htup_details.h>
1010
#include <catalog/pg_type.h>
1111
#include <commands/defrem.h>
12+
#include <nodes/miscnodes.h>
1213
#include <nodes/parsenodes.h>
1314
#include <utils/builtins.h>
1415
#include <utils/lsyscache.h>
@@ -83,19 +84,9 @@ ts_with_clause_definition_names(const WithClauseDefinition *args, Size nargs)
8384
return buf.data;
8485
}
8586

86-
/*
87-
* Deserialize and apply the values in a WITH clause based on the on_arg table.
88-
*
89-
* This function will go through every element in def_elems and search for a
90-
* corresponding argument in args, if one is found it will attempt to deserialize
91-
* the argument, using that table elements deserialize function, then apply it
92-
* to state.
93-
*
94-
* This is used to turn the list into a form more useful for our internal
95-
* functions
96-
*/
97-
WithClauseResult *
98-
ts_with_clauses_parse(const List *def_elems, const WithClauseDefinition *args, Size nargs)
87+
static WithClauseResult *
88+
with_clauses_parse_internal(const List *def_elems, const WithClauseDefinition *args, Size nargs,
89+
bool parse_values)
9990
{
10091
ListCell *cell;
10192
WithClauseResult *results = palloc0(sizeof(*results) * nargs);
@@ -130,7 +121,11 @@ ts_with_clauses_parse(const List *def_elems, const WithClauseDefinition *args, S
130121
def->defname)));
131122
}
132123

133-
results[i].parsed = parse_arg(args[i], def);
124+
/* RESET clauses carry only option names, so skip value parsing */
125+
if (parse_values)
126+
{
127+
results[i].parsed = parse_arg(args[i], def);
128+
}
134129
results[i].is_default = false;
135130
break;
136131
}
@@ -150,6 +145,23 @@ ts_with_clauses_parse(const List *def_elems, const WithClauseDefinition *args, S
150145
return results;
151146
}
152147

148+
/*
149+
* Deserialize and apply the values in a WITH clause based on the on_arg table.
150+
*
151+
* This function will go through every element in def_elems and search for a
152+
* corresponding argument in args, if one is found it will attempt to deserialize
153+
* the argument, using that table elements deserialize function, then apply it
154+
* to state.
155+
*
156+
* This is used to turn the list into a form more useful for our internal
157+
* functions
158+
*/
159+
WithClauseResult *
160+
ts_with_clauses_parse(const List *def_elems, const WithClauseDefinition *args, Size nargs)
161+
{
162+
return with_clauses_parse_internal(def_elems, args, nargs, true);
163+
}
164+
153165
/*
154166
* This function handles parsing of WITH clauses for ALTER TABLE RESET.
155167
* Unlike ts_with_clauses_parse, it does not parse any option values,
@@ -158,56 +170,7 @@ ts_with_clauses_parse(const List *def_elems, const WithClauseDefinition *args, S
158170
WithClauseResult *
159171
ts_with_clauses_parse_reset(const List *def_elems, const WithClauseDefinition *args, Size nargs)
160172
{
161-
ListCell *cell;
162-
WithClauseResult *results = palloc0(sizeof(*results) * nargs);
163-
Size i;
164-
165-
for (i = 0; i < nargs; i++)
166-
{
167-
results[i].definition = &args[i];
168-
results[i].parsed = args[i].default_val;
169-
results[i].is_default = true;
170-
}
171-
172-
foreach (cell, def_elems)
173-
{
174-
DefElem *def = (DefElem *) lfirst(cell);
175-
bool argument_recognized = false;
176-
177-
for (i = 0; i < nargs; i++)
178-
{
179-
for (int j = 0; args[i].arg_names[j] != NULL; ++j)
180-
{
181-
if (pg_strcasecmp(def->defname, args[i].arg_names[j]) == 0)
182-
{
183-
argument_recognized = true;
184-
185-
if (!results[i].is_default)
186-
{
187-
ereport(ERROR,
188-
(errcode(ERRCODE_AMBIGUOUS_PARAMETER),
189-
errmsg("duplicate parameter \"%s.%s\"",
190-
def->defnamespace,
191-
def->defname)));
192-
}
193-
194-
results[i].is_default = false;
195-
break;
196-
}
197-
}
198-
}
199-
200-
if (!argument_recognized)
201-
{
202-
ereport(ERROR,
203-
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
204-
errmsg("unrecognized parameter \"%s.%s\"", def->defnamespace, def->defname),
205-
errhint("Valid timescaledb parameters are: %s",
206-
ts_with_clause_definition_names(args, nargs))));
207-
}
208-
}
209-
210-
return results;
173+
return with_clauses_parse_internal(def_elems, args, nargs, false);
211174
}
212175

213176
extern TSDLLEXPORT char *
@@ -227,6 +190,8 @@ parse_arg(WithClauseDefinition arg, DefElem *def)
227190
Datum val;
228191
Oid in_fn;
229192
Oid typIOParam;
193+
FmgrInfo in_fn_info;
194+
ErrorSaveContext escontext = { .type = T_ErrorSaveContext };
230195

231196
if (!OidIsValid(arg.type_id))
232197
{
@@ -256,38 +221,14 @@ parse_arg(WithClauseDefinition arg, DefElem *def)
256221

257222
Assert(OidIsValid(in_fn));
258223

224+
fmgr_info(in_fn, &in_fn_info);
225+
259226
/*
260-
* We could use InputFunctionCallSafe() here but this is just supported
261-
* for PG16 and later, so we opt for checking if the failure is what we
262-
* expected and re-throwing the error otherwise.
227+
* Parse the value with our own error context so we can raise a custom error message for invalid
228+
* input instead of the generic one from the type's input function.
263229
*/
264-
PG_TRY();
265-
{
266-
val = OidInputFunctionCall(in_fn, value, typIOParam, -1);
267-
}
268-
PG_CATCH();
230+
if (!InputFunctionCallSafe(&in_fn_info, value, typIOParam, -1, (Node *) &escontext, &val))
269231
{
270-
const int sqlerrcode = geterrcode();
271-
/*
272-
* We can deal with the Data Exception category and in the Syntax
273-
* Error or Access Rule Violation category, but if the error is an
274-
* insufficient resources category, for example, an out of memory
275-
* error, we should just re-throw it.
276-
*
277-
* Errors in other categories are unlikely, but we cannot do anything
278-
* with them anyway, so just re-throw them as well.
279-
*/
280-
if (ERRCODE_TO_CATEGORY(sqlerrcode) != ERRCODE_DATA_EXCEPTION &&
281-
ERRCODE_TO_CATEGORY(sqlerrcode) != ERRCODE_SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION)
282-
{
283-
PG_RE_THROW();
284-
}
285-
FlushErrorState();
286-
287-
/* We are currently using the ErrorContext, but since we are going to
288-
* raise an error later, there is no reason to switch memory context
289-
* nor restore the resource owner here. */
290-
291232
Form_pg_type typetup;
292233
HeapTuple tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(arg.type_id));
293234
if (!HeapTupleIsValid(tup))
@@ -309,6 +250,6 @@ parse_arg(WithClauseDefinition arg, DefElem *def)
309250
def->defname,
310251
NameStr(typetup->typname))));
311252
}
312-
PG_END_TRY();
253+
313254
return val;
314255
}

tsl/test/shared/expected/with_clause_parser.out

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ ERROR: raised requested error code "53200"
316316
-- Division by zero, "soft" error. Shows invalid value message (with a
317317
-- strange message in this case).
318318
SELECT * FROM test_with_clause_parse('{{"timescaledb", "sqlstate_raise", "22012"}}');
319-
ERROR: invalid value for timescaledb.sqlstate_raise '22012'
319+
ERROR: raised requested error code "22012"
320320
\set ON_ERROR_STOP 1
321321
-- defaults get overridden
322322
SELECT * FROM test_with_clause_parse('{{"timescaledb", "default", "1"}}');

0 commit comments

Comments
 (0)