Skip to content

Commit 5c62687

Browse files
committed
Don't capture hard errors in with-clause parser
When parsing WITH clause arguments for ALTER TABLE to extract compression parameters, syntax errors in the text format are caught using PG_TRY and PG_CATCH and then returning an indication if the parse succeeded or not. If an out-of-memory error occurs inside execution of the input function and this error is caught, it can continue executing and potentially cause cascading out-of-memory errors and in the end exhausting the error stack. This commit solves this by using checking the category of the thrown error and only allow errors in the data exception and the syntax errors and access rules violation category, which are "soft" errors in this case. Hard errors, like out-of-memory errors, are re-thrown allowing the backend to deal with it properly. It also adds tests and test utilities that allow you to raise an error of an arbitrary category when converting a string to the type.
1 parent 672f3ea commit 5c62687

File tree

5 files changed

+350
-191
lines changed

5 files changed

+350
-191
lines changed

.unreleased/pr_7893

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixes: #7893 Don't capture hard errors in with-clause parser

src/with_clause_parser.c

+26
Original file line numberDiff line numberDiff line change
@@ -161,12 +161,38 @@ parse_arg(WithClauseDefinition arg, DefElem *def)
161161

162162
Assert(OidIsValid(in_fn));
163163

164+
/*
165+
* We could use InputFunctionCallSafe() here but this is just supported
166+
* for PG16 and later, so we opt for checking if the failure is what we
167+
* expected and re-throwing the error otherwise.
168+
*/
164169
PG_TRY();
165170
{
166171
val = OidInputFunctionCall(in_fn, value, typIOParam, -1);
167172
}
168173
PG_CATCH();
169174
{
175+
const int sqlerrcode = geterrcode();
176+
/*
177+
* We can deal with the Data Exception category and in the Syntax
178+
* Error or Access Rule Violation category, but if the error is an
179+
* insufficient resources category, for example, an out of memory
180+
* error, we should just re-throw it.
181+
*
182+
* Errors in other categories are unlikely, but we cannot do anything
183+
* with them anyway, so just re-throw them as well.
184+
*/
185+
if (ERRCODE_TO_CATEGORY(sqlerrcode) != ERRCODE_DATA_EXCEPTION &&
186+
ERRCODE_TO_CATEGORY(sqlerrcode) != ERRCODE_SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION)
187+
{
188+
PG_RE_THROW();
189+
}
190+
FlushErrorState();
191+
192+
/* We are currently using the ErrorContext, but since we are going to
193+
* raise an error later, there is no reason to switch memory context
194+
* nor restore the resource owner here. */
195+
170196
Form_pg_type typetup;
171197
HeapTuple tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(arg.type_id));
172198
if (!HeapTupleIsValid(tup))

test/src/test_with_clause_parser.c

+67-2
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,48 @@
1010
#include <commands/defrem.h>
1111
#include <fmgr.h>
1212
#include <funcapi.h>
13+
#include <postgres_ext.h>
1314
#include <utils/array.h>
1415
#include <utils/builtins.h>
16+
#include <utils/elog.h>
1517
#include <utils/lsyscache.h>
1618
#include <utils/memutils.h>
19+
#include <utils/regproc.h>
1720

1821
#include "annotations.h"
1922
#include "export.h"
2023
#include "test_utils.h"
2124
#include "with_clause_parser.h"
2225

26+
TS_FUNCTION_INFO_V1(ts_sqlstate_raise_in);
27+
TS_FUNCTION_INFO_V1(ts_sqlstate_raise_out);
28+
29+
/*
30+
* Input function that will raise the error code give. This means that you can
31+
* trigger an error when reading and converting a string to this type.
32+
*/
33+
Datum
34+
ts_sqlstate_raise_in(PG_FUNCTION_ARGS)
35+
{
36+
char *code = PG_GETARG_CSTRING(0);
37+
if (strlen(code) != 5)
38+
ereport(ERROR,
39+
errcode(ERRCODE_SYNTAX_ERROR),
40+
errmsg("error code \"%s\" was not of length 5", code));
41+
int sqlstate = MAKE_SQLSTATE(code[0], code[1], code[2], code[3], code[4]);
42+
ereport(ERROR, errcode(sqlstate), errmsg("Raised requested error code \"%s\"", code));
43+
return 0;
44+
}
45+
46+
/*
47+
* Dummy function, we do not store values of this type anywhere.
48+
*/
49+
Datum
50+
ts_sqlstate_raise_out(PG_FUNCTION_ARGS)
51+
{
52+
PG_RETURN_CSTRING("uninteresting");
53+
}
54+
2355
static DefElem *
2456
def_elem_from_texts(Datum *texts, int nelems)
2557
{
@@ -177,6 +209,7 @@ typedef enum TestArgs
177209
TestArgDefault,
178210
TestArgName,
179211
TestArgRegclass,
212+
TestArgRaise,
180213
} TestArgs;
181214

182215
static WithClauseDefinition test_args[] = {
@@ -188,8 +221,14 @@ static WithClauseDefinition test_args[] = {
188221
.type_id = INT4OID,
189222
.default_val = (Datum)-100 },
190223
[TestArgName] = { .arg_names = {"name", NULL}, .type_id = NAMEOID, },
191-
[TestArgRegclass] = { .arg_names = {"regclass", NULL},
192-
.type_id = REGCLASSOID },
224+
[TestArgRegclass] = {
225+
.arg_names = {"regclass", NULL},
226+
.type_id = REGCLASSOID,
227+
},
228+
[TestArgRaise] = {
229+
.arg_names = {"sqlstate_raise", NULL},
230+
.type_id = InvalidOid,
231+
},
193232
};
194233

195234
typedef struct WithClauseValue
@@ -207,6 +246,32 @@ TS_TEST_FN(ts_test_with_clause_parse)
207246
HeapTuple tuple;
208247
WithClauseValue *result;
209248

249+
/*
250+
* Look up any missing type ids before using it below to allow
251+
* user-defined types.
252+
*
253+
* Note that this will not look up types we have found in previous calls
254+
* of this function.
255+
*
256+
* We use the slightly more complicated way of calling to_regtype since
257+
* that exists on all versions of PostgreSQL. We cannot use regtypein
258+
* since that can generate errors and we do not want to deal with that.
259+
*/
260+
for (unsigned int i = 0; i < TS_ARRAY_LEN(test_args); ++i)
261+
{
262+
LOCAL_FCINFO(fcinfo_in, 1);
263+
Datum result;
264+
if (!OidIsValid(test_args[i].type_id))
265+
{
266+
InitFunctionCallInfoData(*fcinfo_in, NULL, 1, InvalidOid, NULL, NULL);
267+
fcinfo_in->args[0].value = CStringGetTextDatum(test_args[i].arg_names[0]);
268+
fcinfo_in->args[0].isnull = false;
269+
result = to_regtype(fcinfo_in);
270+
if (!fcinfo_in->isnull)
271+
test_args[i].type_id = DatumGetObjectId(result);
272+
}
273+
}
274+
210275
if (SRF_IS_FIRSTCALL())
211276
{
212277
ArrayType *with_clause_array;

0 commit comments

Comments
 (0)