Skip to content

Commit c4ed99a

Browse files
committed
Update pltsql_insert_exec_validate_column_count to use SPI_prepare_params instead of SPI_prepare and stop suppressing errors
Prepare the source statement through SPI_prepare_params with the PL/tsql parser setup so @variables resolve and no errors are suppressed, and cache the plan in expr->plan for reuse.
1 parent 88632d9 commit c4ed99a

3 files changed

Lines changed: 43 additions & 69 deletions

File tree

contrib/babelfishpg_tsql/src/pl_exec.c

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4850,22 +4850,13 @@ exec_stmt_execsql(PLtsql_execstate *estate,
48504850
}
48514851

48524852
/*
4853-
* For INSERT EXEC (new path), validate column count BEFORE plan
4854-
* preparation so column mismatch errors take priority over runtime errors
4855-
* (e.g., 1/0). PostgreSQL's eval_const_expressions() would evaluate
4856-
* expressions first.
4857-
*
4858-
* Only validate inside TRY blocks; system procedures like sp_columns
4859-
* have internal SELECTs with varying column counts outside TRY blocks.
4853+
* INSERT EXEC (new path): validate the source statement's result column
4854+
* count against the temp buffer BEFORE it is planned/executed, so a
4855+
* column-count mismatch is raised ahead of any runtime error (e.g. 1/0)
48604856
*/
48614857
if (pltsql_insert_exec_active() &&
48624858
is_part_of_pltsql_trycatch_block(estate))
4863-
{
4864-
if (stmt->sqlstmt && stmt->sqlstmt->query)
4865-
{
4866-
pltsql_insert_exec_validate_column_count_from_query(stmt->sqlstmt->query);
4867-
}
4868-
}
4859+
pltsql_insert_exec_validate_column_count(estate, stmt);
48694860

48704861
PG_TRY();
48714862
{

contrib/babelfishpg_tsql/src/pl_insert_exec.c

Lines changed: 38 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -323,104 +323,87 @@ pltsql_insert_exec_open_target_table(const char *target_table,
323323
* Validate column count from query string BEFORE plan preparation
324324
*/
325325
void
326-
pltsql_insert_exec_validate_column_count_from_query(const char *query_string)
326+
pltsql_insert_exec_validate_column_count(PLtsql_execstate *estate, PLtsql_stmt_execsql *stmt)
327327
{
328-
SPIPlanPtr plan = NULL;
328+
PLtsql_expr *expr = stmt->sqlstmt;
329+
SPIPlanPtr plan;
330+
List *plansources;
329331
CachedPlanSource *plansource;
330332
TupleDesc result_desc;
331333
int query_natts;
332334
Oid temp_table_oid;
333335
Relation temp_rel;
334336
TupleDesc temp_tupdesc;
335337
int temp_natts;
336-
MemoryContext oldcontext;
337338

338339
/* Caller must ensure INSERT EXEC is active before calling */
339340
Assert(pltsql_insert_exec_active());
340341

341342
/*
342343
* Temp table must exist. It is created during INSERT EXEC setup before any
343-
* procedure-body statement runs, so it is normally valid here
344+
* procedure-body statement runs, so it is normally valid here.
344345
*/
345346
temp_table_oid = insert_exec_ctx->temp_table_oid;
346347
if (!OidIsValid(temp_table_oid))
347348
return;
348349

350+
if (expr == NULL || expr->query == NULL)
351+
return;
352+
349353
/*
350-
* Parse-analyze the query to obtain its result descriptor. SPI_prepare
351-
* stops before planning, so constant expressions are not evaluated and a
352-
* runtime error like division by zero will not fire here. On any error,
353-
* defer to the normal execution path.
354+
* Reuse the cached plan if the statement was already prepared (earlier
355+
* execution). Otherwise prepare it once, through the normal parser setup.
354356
*/
355-
oldcontext = CurrentMemoryContext;
356-
357-
PG_TRY();
358-
{
359-
plan = SPI_prepare(query_string, 0, NULL);
360-
}
361-
PG_CATCH();
357+
if (expr->plan != NULL)
358+
plan = expr->plan;
359+
else
362360
{
363-
/*
364-
* Probe-only failure: defer to real execution, which re-raises it.
365-
* Re-throw cancellation/OOM immediately - those must be honored now
366-
*/
367-
ErrorData *edata;
368-
MemoryContextSwitchTo(oldcontext);
369-
edata = CopyErrorData();
370-
FlushErrorState();
371-
372-
if (edata->sqlerrcode == ERRCODE_QUERY_CANCELED ||
373-
edata->sqlerrcode == ERRCODE_ADMIN_SHUTDOWN ||
374-
edata->sqlerrcode == ERRCODE_OUT_OF_MEMORY)
375-
{
376-
ReThrowError(edata);
377-
}
378-
FreeErrorData(edata);
379-
return; /* Parse/analyze error - normal path will report it */
361+
expr->func = estate->func;
362+
plan = SPI_prepare_params(expr->query,
363+
(ParserSetupHook) pltsql_parser_setup,
364+
(void *) expr,
365+
CURSOR_OPT_PARALLEL_OK);
366+
if (plan == NULL)
367+
return;
380368
}
381-
PG_END_TRY();
369+
370+
plansources = SPI_plan_get_plan_sources(plan);
382371

383372
/* Expect exactly one analyzed statement with a known result shape */
384-
if (plan == NULL || list_length(plan->plancache_list) != 1)
385-
{
386-
if (plan != NULL)
387-
SPI_freeplan(plan);
388-
return; /* Multiple statements or unusable plan, defer to runtime */
389-
}
373+
if (list_length(plansources) != 1)
374+
return;
390375

391-
plansource = (CachedPlanSource *) linitial(plan->plancache_list);
376+
plansource = (CachedPlanSource *) linitial(plansources);
392377
result_desc = plansource->resultDesc;
393378

394-
/*
395-
* resultDesc is NULL for statements that do not return tuples (e.g. EXEC).
396-
* In that case there is nothing to validate statically; defer to runtime.
397-
*/
398379
if (result_desc == NULL)
399-
{
400-
SPI_freeplan(plan);
401380
return;
402-
}
403381

404382
query_natts = result_desc->natts;
405-
SPI_freeplan(plan);
406383

407384
/* Get temp table column count */
408385
temp_rel = table_open(temp_table_oid, AccessShareLock);
409386
temp_tupdesc = RelationGetDescr(temp_rel);
410387
temp_natts = temp_tupdesc->natts;
411388
table_close(temp_rel, AccessShareLock);
412389

413-
/* Check for column count mismatch */
390+
/* Column count mismatch: raise before execution so it wins over 1/0 etc. */
414391
if (query_natts != temp_natts)
415-
{
416-
/*
417-
* A column mismatch must roll back all rows even when caught by
418-
* TRY-CATCH, unlike data-level errors (e.g. division by zero) which
419-
* only drop the current row.
420-
*/
421392
ereport(ERROR,
422393
(errcode(ERRCODE_DATATYPE_MISMATCH),
423394
errmsg("structure of query does not match function result type")));
395+
396+
/*
397+
* If we prepared the plan here (expr->plan was NULL), keep it and
398+
* cache it so exec_stmt_execsql reuses it instead of preparing again. This
399+
* is a result-returning SELECT, so it is not a modification statement.
400+
*/
401+
if (expr->plan == NULL)
402+
{
403+
SPI_keepplan(plan);
404+
expr->plan = plan;
405+
stmt->mod_stmt = false;
406+
stmt->mod_stmt_tablevar = false;
424407
}
425408
}
426409

contrib/babelfishpg_tsql/src/pltsql.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2545,7 +2545,7 @@ extern bool pltsql_insert_exec_active(void);
25452545
extern bool pltsql_insert_exec_error_at_trycatch_level(void);
25462546
extern void pltsql_insert_exec_open_target_table(const char *target_table,const char *schema_name_in,
25472547
const char *db_name_in);
2548-
extern void pltsql_insert_exec_validate_column_count_from_query(const char *query_string);
2548+
extern void pltsql_insert_exec_validate_column_count(PLtsql_execstate *estate, PLtsql_stmt_execsql *stmt);
25492549

25502550
/* INSERT EXEC helper functions */
25512551
extern DestReceiver *CreateInsertExecDestReceiver(void);

0 commit comments

Comments
 (0)