Add the ability for the core grammar to have more than one parse target.

This patch essentially allows gram.y to implement a family of related
syntax trees, rather than necessarily always parsing a list of SQL
statements.  raw_parser() gains a new argument, enum RawParseMode,
to say what to do.  As proof of concept, add a mode that just parses
a TypeName without any other decoration, and use that to greatly
simplify typeStringToTypeName().

In addition, invent a new SPI entry point SPI_prepare_extended() to
allow SPI users (particularly plpgsql) to get at this new functionality.
In hopes of making this the last variant of SPI_prepare(), set up its
additional arguments as a struct rather than direct arguments, and
promise that future additions to the struct can default to zero.
SPI_prepare_cursor() and SPI_prepare_params() can perhaps go away at
some point.

Discussion: https://postgr.es/m/4165684.1607707277@sss.pgh.pa.us
This commit is contained in:
Tom Lane 2021-01-04 11:03:22 -05:00
parent b49154b3b7
commit 844fe9f159
14 changed files with 268 additions and 83 deletions

View File

@ -1105,6 +1105,11 @@ SPIPlanPtr SPI_prepare_cursor(const char * <parameter>command</parameter>, int <
for the <structfield>options</structfield> field of <structname>DeclareCursorStmt</structname>.
<function>SPI_prepare</function> always takes the cursor options as zero.
</para>
<para>
This function is now deprecated in favor
of <function>SPI_prepare_extended</function>.
</para>
</refsect1>
<refsect1>
@ -1176,6 +1181,122 @@ SPIPlanPtr SPI_prepare_cursor(const char * <parameter>command</parameter>, int <
<!-- *********************************************** -->
<refentry id="spi-spi-prepare-extended">
<indexterm><primary>SPI_prepare_extended</primary></indexterm>
<refmeta>
<refentrytitle>SPI_prepare_extended</refentrytitle>
<manvolnum>3</manvolnum>
</refmeta>
<refnamediv>
<refname>SPI_prepare_extended</refname>
<refpurpose>prepare a statement, without executing it yet</refpurpose>
</refnamediv>
<refsynopsisdiv>
<synopsis>
SPIPlanPtr SPI_prepare_extended(const char * <parameter>command</parameter>,
const SPIPrepareOptions * <parameter>options</parameter>)
</synopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
<function>SPI_prepare_extended</function> creates and returns a prepared
statement for the specified command, but doesn't execute the command.
This function is equivalent to <function>SPI_prepare</function>,
with the addition that the caller can specify options to control
the parsing of external parameter references, as well as other facets
of query parsing and planning.
</para>
</refsect1>
<refsect1>
<title>Arguments</title>
<variablelist>
<varlistentry>
<term><literal>const char * <parameter>command</parameter></literal></term>
<listitem>
<para>
command string
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>const SPIPrepareOptions * <parameter>options</parameter></literal></term>
<listitem>
<para>
struct containing optional arguments
</para>
</listitem>
</varlistentry>
</variablelist>
<para>
Callers should always zero out the entire <parameter>options</parameter>
struct, then fill whichever fields they want to set. This ensures forward
compatibility of code, since any fields that are added to the struct in
future will be defined to behave backwards-compatibly if they are zero.
The currently available <parameter>options</parameter> fields are:
</para>
<variablelist>
<varlistentry>
<term><literal>ParserSetupHook <parameter>parserSetup</parameter></literal></term>
<listitem>
<para>
Parser hook setup function
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>void * <parameter>parserSetupArg</parameter></literal></term>
<listitem>
<para>
pass-through argument for <parameter>parserSetup</parameter>
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>RawParseMode <parameter>parseMode</parameter></literal></term>
<listitem>
<para>
mode for raw parsing; <literal>RAW_PARSE_DEFAULT</literal> (zero)
produces default behavior
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>int <parameter>cursorOptions</parameter></literal></term>
<listitem>
<para>
integer bit mask of cursor options; zero produces default behavior
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Return Value</title>
<para>
<function>SPI_prepare_extended</function> has the same return conventions as
<function>SPI_prepare</function>.
</para>
</refsect1>
</refentry>
<!-- *********************************************** -->
<refentry id="spi-spi-prepare-params">
<indexterm><primary>SPI_prepare_params</primary></indexterm>
@ -1208,6 +1329,11 @@ SPIPlanPtr SPI_prepare_params(const char * <parameter>command</parameter>,
with the addition that the caller can specify parser hook functions
to control the parsing of external parameter references.
</para>
<para>
This function is now deprecated in favor
of <function>SPI_prepare_extended</function>.
</para>
</refsect1>
<refsect1>

View File

@ -12095,7 +12095,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
* parse_analyze() or the rewriter, but instead we need to pass them
* through parse_utilcmd.c to make them ready for execution.
*/
raw_parsetree_list = raw_parser(cmd);
raw_parsetree_list = raw_parser(cmd, RAW_PARSE_DEFAULT);
querytree_list = NIL;
foreach(list_item, raw_parsetree_list)
{

View File

@ -508,6 +508,7 @@ SPI_execute(const char *src, bool read_only, long tcount)
memset(&plan, 0, sizeof(_SPI_plan));
plan.magic = _SPI_PLAN_MAGIC;
plan.parse_mode = RAW_PARSE_DEFAULT;
plan.cursor_options = CURSOR_OPT_PARALLEL_OK;
_SPI_prepare_oneshot_plan(src, &plan);
@ -681,6 +682,7 @@ SPI_execute_with_args(const char *src,
memset(&plan, 0, sizeof(_SPI_plan));
plan.magic = _SPI_PLAN_MAGIC;
plan.parse_mode = RAW_PARSE_DEFAULT;
plan.cursor_options = CURSOR_OPT_PARALLEL_OK;
plan.nargs = nargs;
plan.argtypes = argtypes;
@ -726,6 +728,7 @@ SPI_execute_with_receiver(const char *src,
memset(&plan, 0, sizeof(_SPI_plan));
plan.magic = _SPI_PLAN_MAGIC;
plan.parse_mode = RAW_PARSE_DEFAULT;
plan.cursor_options = CURSOR_OPT_PARALLEL_OK;
if (params)
{
@ -768,6 +771,7 @@ SPI_prepare_cursor(const char *src, int nargs, Oid *argtypes,
memset(&plan, 0, sizeof(_SPI_plan));
plan.magic = _SPI_PLAN_MAGIC;
plan.parse_mode = RAW_PARSE_DEFAULT;
plan.cursor_options = cursorOptions;
plan.nargs = nargs;
plan.argtypes = argtypes;
@ -784,6 +788,42 @@ SPI_prepare_cursor(const char *src, int nargs, Oid *argtypes,
return result;
}
SPIPlanPtr
SPI_prepare_extended(const char *src,
const SPIPrepareOptions *options)
{
_SPI_plan plan;
SPIPlanPtr result;
if (src == NULL || options == NULL)
{
SPI_result = SPI_ERROR_ARGUMENT;
return NULL;
}
SPI_result = _SPI_begin_call(true);
if (SPI_result < 0)
return NULL;
memset(&plan, 0, sizeof(_SPI_plan));
plan.magic = _SPI_PLAN_MAGIC;
plan.parse_mode = options->parseMode;
plan.cursor_options = options->cursorOptions;
plan.nargs = 0;
plan.argtypes = NULL;
plan.parserSetup = options->parserSetup;
plan.parserSetupArg = options->parserSetupArg;
_SPI_prepare_plan(src, &plan);
/* copy plan to procedure context */
result = _SPI_make_plan_non_temp(&plan);
_SPI_end_call(true);
return result;
}
SPIPlanPtr
SPI_prepare_params(const char *src,
ParserSetupHook parserSetup,
@ -805,6 +845,7 @@ SPI_prepare_params(const char *src,
memset(&plan, 0, sizeof(_SPI_plan));
plan.magic = _SPI_PLAN_MAGIC;
plan.parse_mode = RAW_PARSE_DEFAULT;
plan.cursor_options = cursorOptions;
plan.nargs = 0;
plan.argtypes = NULL;
@ -1340,6 +1381,7 @@ SPI_cursor_open_with_args(const char *name,
memset(&plan, 0, sizeof(_SPI_plan));
plan.magic = _SPI_PLAN_MAGIC;
plan.parse_mode = RAW_PARSE_DEFAULT;
plan.cursor_options = cursorOptions;
plan.nargs = nargs;
plan.argtypes = argtypes;
@ -1400,6 +1442,7 @@ SPI_cursor_parse_open_with_paramlist(const char *name,
memset(&plan, 0, sizeof(_SPI_plan));
plan.magic = _SPI_PLAN_MAGIC;
plan.parse_mode = RAW_PARSE_DEFAULT;
plan.cursor_options = cursorOptions;
if (params)
{
@ -2036,7 +2079,8 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self)
* Parse and analyze a querystring.
*
* At entry, plan->argtypes and plan->nargs (or alternatively plan->parserSetup
* and plan->parserSetupArg) must be valid, as must plan->cursor_options.
* and plan->parserSetupArg) must be valid, as must plan->parse_mode and
* plan->cursor_options.
*
* Results are stored into *plan (specifically, plan->plancache_list).
* Note that the result data is all in CurrentMemoryContext or child contexts
@ -2063,7 +2107,7 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan)
/*
* Parse the request string into a list of raw parse trees.
*/
raw_parsetree_list = pg_parse_query(src);
raw_parsetree_list = raw_parser(src, plan->parse_mode);
/*
* Do parse analysis and rule rewrite for each raw parsetree, storing the
@ -2168,7 +2212,7 @@ _SPI_prepare_oneshot_plan(const char *src, SPIPlanPtr plan)
/*
* Parse the request string into a list of raw parse trees.
*/
raw_parsetree_list = pg_parse_query(src);
raw_parsetree_list = raw_parser(src, plan->parse_mode);
/*
* Construct plancache entries, but don't do parse analysis yet.
@ -2866,6 +2910,7 @@ _SPI_make_plan_non_temp(SPIPlanPtr plan)
newplan = (SPIPlanPtr) palloc0(sizeof(_SPI_plan));
newplan->magic = _SPI_PLAN_MAGIC;
newplan->plancxt = plancxt;
newplan->parse_mode = plan->parse_mode;
newplan->cursor_options = plan->cursor_options;
newplan->nargs = plan->nargs;
if (plan->nargs > 0)
@ -2930,6 +2975,7 @@ _SPI_save_plan(SPIPlanPtr plan)
newplan = (SPIPlanPtr) palloc0(sizeof(_SPI_plan));
newplan->magic = _SPI_PLAN_MAGIC;
newplan->plancxt = plancxt;
newplan->parse_mode = plan->parse_mode;
newplan->cursor_options = plan->cursor_options;
newplan->nargs = plan->nargs;
if (plan->nargs > 0)

View File

@ -384,7 +384,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> vacuum_relation
%type <selectlimit> opt_select_limit select_limit limit_clause
%type <list> stmtblock stmtmulti
%type <list> parse_toplevel stmtmulti
OptTableElementList TableElementList OptInherit definition
OptTypedTableElementList TypedTableElementList
reloptions opt_reloptions
@ -723,6 +723,15 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
*/
%token NOT_LA NULLS_LA WITH_LA
/*
* The grammar likewise thinks these tokens are keywords, but they are never
* generated by the scanner. Rather, they can be injected by parser.c as
* the initial token of the string (using the lookahead-token mechanism
* implemented there). This provides a way to tell the grammar to parse
* something other than the usual list of SQL commands.
*/
%token MODE_TYPE_NAME
/* Precedence: lowest to highest */
%nonassoc SET /* see relation_expr_opt_alias */
@ -787,11 +796,20 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
/*
* The target production for the whole parse.
*
* Ordinarily we parse a list of statements, but if we see one of the
* special MODE_XXX symbols as first token, we parse something else.
* The options here correspond to enum RawParseMode, which see for details.
*/
stmtblock: stmtmulti
parse_toplevel:
stmtmulti
{
pg_yyget_extra(yyscanner)->parsetree = $1;
}
| MODE_TYPE_NAME Typename
{
pg_yyget_extra(yyscanner)->parsetree = list_make1($2);
}
;
/*

View File

@ -1541,7 +1541,7 @@ select_common_typmod(ParseState *pstate, List *exprs, Oid common_type)
foreach(lc, exprs)
{
Node *expr = (Node *) lfirst(lc);
Node *expr = (Node *) lfirst(lc);
/* Types must match */
if (exprType(expr) != common_type)
@ -2380,7 +2380,8 @@ enforce_generic_type_consistency(const Oid *actual_arg_types,
if (!OidIsValid(elem_typeid))
{
/*
* if we don't have an element type yet, use the one we just got
* if we don't have an element type yet, use the one we just
* got
*/
elem_typeid = range_typelem;
}

View File

@ -719,13 +719,6 @@ pts_error_callback(void *arg)
const char *str = (const char *) arg;
errcontext("invalid type name \"%s\"", str);
/*
* Currently we just suppress any syntax error position report, rather
* than transforming to an "internal query" error. It's unlikely that a
* type name is complex enough to need positioning.
*/
errposition(0);
}
/*
@ -737,11 +730,7 @@ pts_error_callback(void *arg)
TypeName *
typeStringToTypeName(const char *str)
{
StringInfoData buf;
List *raw_parsetree_list;
SelectStmt *stmt;
ResTarget *restarget;
TypeCast *typecast;
TypeName *typeName;
ErrorContextCallback ptserrcontext;
@ -749,9 +738,6 @@ typeStringToTypeName(const char *str)
if (strspn(str, " \t\n\r\f") == strlen(str))
goto fail;
initStringInfo(&buf);
appendStringInfo(&buf, "SELECT NULL::%s", str);
/*
* Setup error traceback support in case of ereport() during parse
*/
@ -760,58 +746,18 @@ typeStringToTypeName(const char *str)
ptserrcontext.previous = error_context_stack;
error_context_stack = &ptserrcontext;
raw_parsetree_list = raw_parser(buf.data);
raw_parsetree_list = raw_parser(str, RAW_PARSE_TYPE_NAME);
error_context_stack = ptserrcontext.previous;
/*
* Make sure we got back exactly what we expected and no more; paranoia is
* justified since the string might contain anything.
*/
if (list_length(raw_parsetree_list) != 1)
goto fail;
stmt = (SelectStmt *) linitial_node(RawStmt, raw_parsetree_list)->stmt;
if (stmt == NULL ||
!IsA(stmt, SelectStmt) ||
stmt->distinctClause != NIL ||
stmt->intoClause != NULL ||
stmt->fromClause != NIL ||
stmt->whereClause != NULL ||
stmt->groupClause != NIL ||
stmt->havingClause != NULL ||
stmt->windowClause != NIL ||
stmt->valuesLists != NIL ||
stmt->sortClause != NIL ||
stmt->limitOffset != NULL ||
stmt->limitCount != NULL ||
stmt->lockingClause != NIL ||
stmt->withClause != NULL ||
stmt->op != SETOP_NONE)
goto fail;
if (list_length(stmt->targetList) != 1)
goto fail;
restarget = (ResTarget *) linitial(stmt->targetList);
if (restarget == NULL ||
!IsA(restarget, ResTarget) ||
restarget->name != NULL ||
restarget->indirection != NIL)
goto fail;
typecast = (TypeCast *) restarget->val;
if (typecast == NULL ||
!IsA(typecast, TypeCast) ||
typecast->arg == NULL ||
!IsA(typecast->arg, A_Const))
goto fail;
/* We should get back exactly one TypeName node. */
Assert(list_length(raw_parsetree_list) == 1);
typeName = linitial_node(TypeName, raw_parsetree_list);
typeName = typecast->typeName;
if (typeName == NULL ||
!IsA(typeName, TypeName))
goto fail;
/* The grammar allows SETOF in TypeName, but we don't want that here. */
if (typeName->setof)
goto fail;
pfree(buf.data);
return typeName;
fail:

View File

@ -35,11 +35,11 @@ static char *str_udeescape(const char *str, char escape,
* raw_parser
* Given a query in string form, do lexical and grammatical analysis.
*
* Returns a list of raw (un-analyzed) parse trees. The immediate elements
* of the list are always RawStmt nodes.
* Returns a list of raw (un-analyzed) parse trees. The contents of the
* list have the form required by the specified RawParseMode.
*/
List *
raw_parser(const char *str)
raw_parser(const char *str, RawParseMode mode)
{
core_yyscan_t yyscanner;
base_yy_extra_type yyextra;
@ -49,8 +49,22 @@ raw_parser(const char *str)
yyscanner = scanner_init(str, &yyextra.core_yy_extra,
&ScanKeywords, ScanKeywordTokens);
/* base_yylex() only needs this much initialization */
yyextra.have_lookahead = false;
/* base_yylex() only needs us to initialize the lookahead token, if any */
if (mode == RAW_PARSE_DEFAULT)
yyextra.have_lookahead = false;
else
{
/* this array is indexed by RawParseMode enum */
static const int mode_token[] = {
0, /* RAW_PARSE_DEFAULT */
MODE_TYPE_NAME /* RAW_PARSE_TYPE_NAME */
};
yyextra.have_lookahead = true;
yyextra.lookahead_token = mode_token[mode];
yyextra.lookahead_yylloc = 0;
yyextra.lookahead_end = NULL;
}
/* initialize the bison parser */
parser_init(&yyextra);
@ -104,7 +118,8 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
cur_token = yyextra->lookahead_token;
lvalp->core_yystype = yyextra->lookahead_yylval;
*llocp = yyextra->lookahead_yylloc;
*(yyextra->lookahead_end) = yyextra->lookahead_hold_char;
if (yyextra->lookahead_end)
*(yyextra->lookahead_end) = yyextra->lookahead_hold_char;
yyextra->have_lookahead = false;
}
else

View File

@ -635,7 +635,7 @@ pg_parse_query(const char *query_string)
if (log_parser_stats)
ResetUsage();
raw_parsetree_list = raw_parser(query_string);
raw_parsetree_list = raw_parser(query_string, RAW_PARSE_DEFAULT);
if (log_parser_stats)
ShowUsage("PARSER STATISTICS");

View File

@ -15,7 +15,7 @@
#include "commands/trigger.h"
#include "lib/ilist.h"
#include "nodes/parsenodes.h"
#include "parser/parser.h"
#include "utils/portal.h"
@ -33,6 +33,15 @@ typedef struct SPITupleTable
SubTransactionId subid; /* subxact in which tuptable was created */
} SPITupleTable;
/* Optional arguments for SPI_prepare_extended */
typedef struct SPIPrepareOptions
{
ParserSetupHook parserSetup;
void *parserSetupArg;
RawParseMode parseMode;
int cursorOptions;
} SPIPrepareOptions;
/* Plans are opaque structs for standard users of SPI */
typedef struct _SPI_plan *SPIPlanPtr;
@ -113,6 +122,8 @@ extern int SPI_execute_with_receiver(const char *src,
extern SPIPlanPtr SPI_prepare(const char *src, int nargs, Oid *argtypes);
extern SPIPlanPtr SPI_prepare_cursor(const char *src, int nargs, Oid *argtypes,
int cursorOptions);
extern SPIPlanPtr SPI_prepare_extended(const char *src,
const SPIPrepareOptions *options);
extern SPIPlanPtr SPI_prepare_params(const char *src,
ParserSetupHook parserSetup,
void *parserSetupArg,

View File

@ -95,6 +95,7 @@ typedef struct _SPI_plan
bool no_snapshots; /* let the caller handle the snapshots */
List *plancache_list; /* one CachedPlanSource per parsetree */
MemoryContext plancxt; /* Context containing _SPI_plan and data */
RawParseMode parse_mode; /* raw_parser() mode */
int cursor_options; /* Cursor options used for planning */
int nargs; /* number of plan arguments */
Oid *argtypes; /* Argument types (NULL if nargs is 0) */

View File

@ -18,6 +18,24 @@
#include "nodes/parsenodes.h"
/*
* RawParseMode determines the form of the string that raw_parser() accepts:
*
* RAW_PARSE_DEFAULT: parse a semicolon-separated list of SQL commands,
* and return a List of RawStmt nodes.
*
* RAW_PARSE_TYPE_NAME: parse a type name, and return a one-element List
* containing a TypeName node.
*
* ... more to come ...
*/
typedef enum
{
RAW_PARSE_DEFAULT = 0,
RAW_PARSE_TYPE_NAME
} RawParseMode;
/* Values for the backslash_quote GUC */
typedef enum
{
BACKSLASH_QUOTE_OFF,
@ -32,7 +50,7 @@ extern PGDLLIMPORT bool standard_conforming_strings;
/* Primary entry point for the raw parsing functions */
extern List *raw_parser(const char *str);
extern List *raw_parser(const char *str, RawParseMode mode);
/* Utility functions exported by gram.y (perhaps these should be elsewhere) */
extern List *SystemFuncName(char *name);

View File

@ -63,7 +63,7 @@ my %replace_types = (
'opt_array_bounds' => '<index>',
# "ignore" means: do not create type and rules for this non-term-id
'stmtblock' => 'ignore',
'parse_toplevel' => 'ignore',
'stmtmulti' => 'ignore',
'CreateAsStmt' => 'ignore',
'DeallocateStmt' => 'ignore',

View File

@ -4168,6 +4168,7 @@ exec_prepare_plan(PLpgSQL_execstate *estate,
bool keepplan)
{
SPIPlanPtr plan;
SPIPrepareOptions options;
/*
* The grammar can't conveniently set expr->func while building the parse
@ -4178,12 +4179,14 @@ exec_prepare_plan(PLpgSQL_execstate *estate,
/*
* Generate and save the plan
*/
plan = SPI_prepare_params(expr->query,
(ParserSetupHook) plpgsql_parser_setup,
(void *) expr,
cursorOptions);
memset(&options, 0, sizeof(options));
options.parserSetup = (ParserSetupHook) plpgsql_parser_setup;
options.parserSetupArg = (void *) expr;
options.parseMode = RAW_PARSE_DEFAULT;
options.cursorOptions = cursorOptions;
plan = SPI_prepare_extended(expr->query, &options);
if (plan == NULL)
elog(ERROR, "SPI_prepare_params failed for \"%s\": %s",
elog(ERROR, "SPI_prepare_extended failed for \"%s\": %s",
expr->query, SPI_result_code_string(SPI_result));
if (keepplan)
SPI_keepplan(plan);

View File

@ -3661,7 +3661,7 @@ check_sql_expr(const char *stmt, int location, int leaderlen)
error_context_stack = &syntax_errcontext;
oldCxt = MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
(void) raw_parser(stmt);
(void) raw_parser(stmt, RAW_PARSE_DEFAULT);
MemoryContextSwitchTo(oldCxt);
/* Restore former ereport callback */