summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Lane2006-08-12 20:05:56 +0000
committerTom Lane2006-08-12 20:05:56 +0000
commitbc116b3ab8c3e865235e6037163768687acc1335 (patch)
tree260b715174cc86aaf4f819fc0e179f6bb9e7c8d6
parent539c0a35094d6f0091e612dcc85a302a5b3fd76b (diff)
Tweak SPI_cursor_open to allow INSERT/UPDATE/DELETE RETURNING; this was
merely a matter of fixing the error check, since the underlying Portal infrastructure already handles it. This in turn allows these statements to be used in some existing plpgsql and plperl contexts, such as a plpgsql FOR loop. Also, do some marginal code cleanup in places that were being sloppy about distinguishing SELECT from SELECT INTO.
-rw-r--r--doc/src/sgml/plperl.sgml50
-rw-r--r--doc/src/sgml/plpgsql.sgml25
-rw-r--r--doc/src/sgml/spi.sgml26
-rw-r--r--src/backend/commands/portalcmds.c2
-rw-r--r--src/backend/executor/execMain.c2
-rw-r--r--src/backend/executor/functions.c18
-rw-r--r--src/backend/executor/spi.c55
-rw-r--r--src/backend/optimizer/prep/prepjointree.c2
-rw-r--r--src/backend/optimizer/util/clauses.c1
-rw-r--r--src/backend/parser/analyze.c9
-rw-r--r--src/backend/parser/parse_clause.c2
-rw-r--r--src/backend/parser/parse_expr.c2
-rw-r--r--src/backend/rewrite/rewriteDefine.c3
-rw-r--r--src/backend/tcop/utility.c32
-rw-r--r--src/include/nodes/parsenodes.h3
-rw-r--r--src/include/tcop/utility.h2
16 files changed, 136 insertions, 98 deletions
diff --git a/doc/src/sgml/plperl.sgml b/doc/src/sgml/plperl.sgml
index d7e4090159..ca78f40f38 100644
--- a/doc/src/sgml/plperl.sgml
+++ b/doc/src/sgml/plperl.sgml
@@ -244,18 +244,8 @@ $$ LANGUAGE plperl;
SELECT * FROM perl_set();
</programlisting>
-
</para>
- <para>
- <application>PL/Perl</> does not currently have full support for
- domain types: it treats a domain the same as the underlying scalar
- type. This means that constraints associated with the domain will
- not be enforced. This is not an issue for function arguments, but
- it is a hazard if you declare a <application>PL/Perl</> function
- as returning a domain type.
- </para>
-
<para>
If you wish to use the <literal>strict</> pragma with your code,
the easiest way to do so is to <command>SET</>
@@ -439,26 +429,26 @@ SELECT * from lotsa_md5(500);
<para>
The advantage of prepared queries is that is it possible to use one prepared plan for more
- than one query execution. After the plan is not needed anymore, it must be freed with
+ than one query execution. After the plan is not needed anymore, it may be freed with
<literal>spi_freeplan</literal>:
</para>
<para>
<programlisting>
CREATE OR REPLACE FUNCTION init() RETURNS INTEGER AS $$
- $_SHARED{my_plan} = spi_prepare( 'SELECT (now() + $1)::date AS now', 'INTERVAL');
+ $_SHARED{my_plan} = spi_prepare( 'SELECT (now() + $1)::date AS now', 'INTERVAL');
$$ LANGUAGE plperl;
CREATE OR REPLACE FUNCTION add_time( INTERVAL ) RETURNS TEXT AS $$
- return spi_exec_prepared(
- $_SHARED{my_plan},
- $_[0],
- )->{rows}->[0]->{now};
+ return spi_exec_prepared(
+ $_SHARED{my_plan},
+ $_[0],
+ )->{rows}->[0]->{now};
$$ LANGUAGE plperl;
CREATE OR REPLACE FUNCTION done() RETURNS INTEGER AS $$
- spi_freeplan( $_SHARED{my_plan});
- undef $_SHARED{my_plan};
+ spi_freeplan( $_SHARED{my_plan});
+ undef $_SHARED{my_plan};
$$ LANGUAGE plperl;
SELECT init();
@@ -478,16 +468,14 @@ SELECT done();
</para>
<para>
- <literal>spi_cursor_close</literal> can be used to abort sequence of
- <literal>spi_fetchrow</literal> calls. Normally, the call to
- <literal>spi_fetchrow</literal> that returns <literal>undef</literal> is
- the signal that there are no more rows to read. Also
- that call automatically frees the cursor associated with the query. If it is desired not
- to read all retuned rows, <literal>spi_cursor_close</literal> must be
- called to avoid memory leaks.
+ Normally, <function>spi_fetchrow</> should be repeated until it
+ returns <literal>undef</literal>, indicating that there are no more
+ rows to read. The cursor is automatically freed when
+ <function>spi_fetchrow</> returns <literal>undef</literal>.
+ If you do not wish to read all the rows, instead call
+ <function>spi_cursor_close</> to free the cursor.
+ Failure to do so will result in memory leaks.
</para>
-
-
</listitem>
</varlistentry>
@@ -630,8 +618,8 @@ CREATE FUNCTION badfunc() RETURNS integer AS $$
return 1;
$$ LANGUAGE plperl;
</programlisting>
- The creation of this function will fail as its use of a forbidden
- operation will be be caught by the validator.
+ The creation of this function will fail as its use of a forbidden
+ operation will be be caught by the validator.
</para>
<para>
@@ -748,8 +736,8 @@ $$ LANGUAGE plperl;
<listitem>
<para>
Name of the table on which the trigger fired. This has been deprecated,
- and could be removed in a future release.
- Please use $_TD-&gt;{table_name} instead.
+ and could be removed in a future release.
+ Please use $_TD-&gt;{table_name} instead.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index ef76a83688..9a570e5413 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -2040,9 +2040,8 @@ END LOOP <optional> <replaceable>label</replaceable> </optional>;
The <replaceable>target</replaceable> is a record variable, row variable,
or comma-separated list of scalar variables.
The <replaceable>target</replaceable> is successively assigned each row
- resulting from the <replaceable>query</replaceable> (which must be a
- <command>SELECT</command> command) and the loop body is executed for each
- row. Here is an example:
+ resulting from the <replaceable>query</replaceable> and the loop body is
+ executed for each row. Here is an example:
<programlisting>
CREATE FUNCTION cs_refresh_mviews() RETURNS integer AS $$
DECLARE
@@ -2070,6 +2069,15 @@ $$ LANGUAGE plpgsql;
</para>
<para>
+ The <replaceable>query</replaceable> used in this type of <literal>FOR</>
+ statement can be any query that returns rows to the caller:
+ <command>SELECT</> (without <literal>INTO</>) is the most common case,
+ but you can also use <command>INSERT</>, <command>UPDATE</>, or
+ <command>DELETE</> with a <literal>RETURNING</> clause. Some utility
+ commands such as <command>EXPLAIN</> will work too.
+ </para>
+
+ <para>
The <literal>FOR-IN-EXECUTE</> statement is another way to iterate over
rows:
<synopsis>
@@ -2078,12 +2086,11 @@ FOR <replaceable>target</replaceable> IN EXECUTE <replaceable>text_expression</r
<replaceable>statements</replaceable>
END LOOP <optional> <replaceable>label</replaceable> </optional>;
</synopsis>
- This is like the previous form, except that the source
- <command>SELECT</command> statement is specified as a string
- expression, which is evaluated and replanned on each entry to
- the <literal>FOR</> loop. This allows the programmer to choose the speed of
- a preplanned query or the flexibility of a dynamic query, just
- as with a plain <command>EXECUTE</command> statement.
+ This is like the previous form, except that the source query
+ is specified as a string expression, which is evaluated and replanned
+ on each entry to the <literal>FOR</> loop. This allows the programmer to
+ choose the speed of a preplanned query or the flexibility of a dynamic
+ query, just as with a plain <command>EXECUTE</command> statement.
</para>
<note>
diff --git a/doc/src/sgml/spi.sgml b/doc/src/sgml/spi.sgml
index e0cd06ab69..4a83aea9b8 100644
--- a/doc/src/sgml/spi.sgml
+++ b/doc/src/sgml/spi.sgml
@@ -535,15 +535,15 @@ typedef struct
<term><symbol>SPI_ERROR_TRANSACTION</symbol></term>
<listitem>
<para>
- if any command involving transaction manipulation was attempted
- (<command>BEGIN</>,
- <command>COMMIT</>,
- <command>ROLLBACK</>,
- <command>SAVEPOINT</>,
- <command>PREPARE TRANSACTION</>,
- <command>COMMIT PREPARED</>,
- <command>ROLLBACK PREPARED</>,
- or any variant thereof)
+ if any command involving transaction manipulation was attempted
+ (<command>BEGIN</>,
+ <command>COMMIT</>,
+ <command>ROLLBACK</>,
+ <command>SAVEPOINT</>,
+ <command>PREPARE TRANSACTION</>,
+ <command>COMMIT PREPARED</>,
+ <command>ROLLBACK PREPARED</>,
+ or any variant thereof)
</para>
</listitem>
</varlistentry>
@@ -917,10 +917,12 @@ bool SPI_is_cursor_plan(void * <parameter>plan</parameter>)
<para>
<function>SPI_is_cursor_plan</function> returns <symbol>true</symbol>
if a plan prepared by <function>SPI_prepare</function> can be passed
- as an argument to <function>SPI_cursor_open</function> and <symbol>
- false</symbol> if that is not the case. The criteria are that the
+ as an argument to <function>SPI_cursor_open</function>, or
+ <symbol>false</symbol> if that is not the case. The criteria are that the
<parameter>plan</parameter> represents one single command and that this
- command is a <command>SELECT</command> without an <command>INTO</command>
+ command returns tuples to the caller; for example, <command>SELECT</>
+ is allowed unless it contains an <literal>INTO</> clause, and
+ <command>UPDATE</> is allowed only if it contains a <literal>RETURNING</>
clause.
</para>
</refsect1>
diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c
index b79ab1cc2c..269e3626f6 100644
--- a/src/backend/commands/portalcmds.c
+++ b/src/backend/commands/portalcmds.c
@@ -87,7 +87,7 @@ PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params)
if (query->into)
ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
+ (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
errmsg("DECLARE CURSOR may not specify INTO")));
if (query->rowMarks != NIL)
ereport(ERROR,
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index a4e673f47b..bf11f724bf 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -497,7 +497,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
/*
* if there is a result relation, initialize result relation stuff
*/
- if (parseTree->resultRelation != 0 && operation != CMD_SELECT)
+ if (parseTree->resultRelation)
{
List *resultRelations = parseTree->resultRelations;
int numResultRelations;
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 8d6122fead..e72c58a271 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -361,7 +361,9 @@ postquel_getnext(execution_state *es)
* run it to completion. (If we run to completion then
* ExecutorRun is guaranteed to return NULL.)
*/
- if (LAST_POSTQUEL_COMMAND(es) && es->qd->operation == CMD_SELECT)
+ if (LAST_POSTQUEL_COMMAND(es) &&
+ es->qd->operation == CMD_SELECT &&
+ es->qd->parsetree->into == NULL)
count = 1L;
else
count = 0L;
@@ -868,7 +870,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
JunkFilter **junkFilter)
{
Query *parse;
- int cmd;
+ bool isSelect;
List *tlist;
ListCell *tlistitem;
int tlistlen;
@@ -893,15 +895,18 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
/* find the final query */
parse = (Query *) lfirst(list_tail(queryTreeList));
- cmd = parse->commandType;
- tlist = parse->targetList;
+ /*
+ * Note: eventually replace this with QueryReturnsTuples? We'd need
+ * a more general method of determining the output type, though.
+ */
+ isSelect = (parse->commandType == CMD_SELECT && parse->into == NULL);
/*
* The last query must be a SELECT if and only if return type isn't VOID.
*/
if (rettype == VOIDOID)
{
- if (cmd == CMD_SELECT)
+ if (isSelect)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("return type mismatch in function declared to return %s",
@@ -911,7 +916,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
}
/* by here, the function is declared to return some type */
- if (cmd != CMD_SELECT)
+ if (!isSelect)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("return type mismatch in function declared to return %s",
@@ -921,6 +926,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
/*
* Count the non-junk entries in the result targetlist.
*/
+ tlist = parse->targetList;
tlistlen = ExecCleanTargetListLength(tlist);
fn_typtype = get_typtype(rettype);
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 77337ebe9c..421c5ab0e2 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -837,26 +837,12 @@ SPI_cursor_open(const char *name, void *plan,
planTree = (Plan *) linitial(ptlist);
/* Must be a query that returns tuples */
- switch (queryTree->commandType)
- {
- case CMD_SELECT:
- if (queryTree->into != NULL)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
- errmsg("cannot open SELECT INTO query as cursor")));
- break;
- case CMD_UTILITY:
- if (!UtilityReturnsTuples(queryTree->utilityStmt))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
- errmsg("cannot open non-SELECT query as cursor")));
- break;
- default:
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
- errmsg("cannot open non-SELECT query as cursor")));
- break;
- }
+ if (!QueryReturnsTuples(queryTree))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
+ /* translator: %s is name of a SQL command, eg INSERT */
+ errmsg("cannot open %s query as cursor",
+ CreateQueryTag(queryTree))));
/* Reset SPI result (note we deliberately don't touch lastoid) */
SPI_processed = 0;
@@ -876,7 +862,7 @@ SPI_cursor_open(const char *name, void *plan,
portal = CreatePortal(name, false, false);
}
- /* Switch to portals memory and copy the parsetree and plan to there */
+ /* Switch to portal's memory and copy the parsetree and plan to there */
oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
queryTree = copyObject(queryTree);
planTree = copyObject(planTree);
@@ -919,9 +905,9 @@ SPI_cursor_open(const char *name, void *plan,
* Set up the portal.
*/
PortalDefineQuery(portal,
- NULL,
+ NULL, /* no statement name */
spiplan->query,
- "SELECT", /* don't have the raw parse tree... */
+ CreateQueryTag(queryTree),
list_make1(queryTree),
list_make1(planTree),
PortalGetHeapMemory(portal));
@@ -954,9 +940,16 @@ SPI_cursor_open(const char *name, void *plan,
*/
PortalStart(portal, paramLI, snapshot);
- Assert(portal->strategy == PORTAL_ONE_SELECT ||
- portal->strategy == PORTAL_ONE_RETURNING ||
- portal->strategy == PORTAL_UTIL_SELECT);
+ /*
+ * If this test fails then we're out of sync with pquery.c about
+ * which queries can return tuples...
+ */
+ if (portal->strategy == PORTAL_MULTI_QUERY)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
+ /* translator: %s is name of a SQL command, eg INSERT */
+ errmsg("cannot open %s query as cursor",
+ CreateQueryTag(queryTree))));
/* Return the created portal */
return portal;
@@ -1046,12 +1039,12 @@ SPI_getargcount(void *plan)
/*
* Returns true if the plan contains exactly one command
- * and that command originates from normal SELECT (i.e.
- * *not* a SELECT ... INTO). In essence, the result indicates
- * if the command can be used with SPI_cursor_open
+ * and that command returns tuples to the caller (eg, SELECT or
+ * INSERT ... RETURNING, but not SELECT ... INTO). In essence,
+ * the result indicates if the command can be used with SPI_cursor_open
*
* Parameters
- * plan A plan previously prepared using SPI_prepare
+ * plan: A plan previously prepared using SPI_prepare
*/
bool
SPI_is_cursor_plan(void *plan)
@@ -1070,7 +1063,7 @@ SPI_is_cursor_plan(void *plan)
{
Query *queryTree = (Query *) linitial((List *) linitial(qtlist));
- if (queryTree->commandType == CMD_SELECT && queryTree->into == NULL)
+ if (QueryReturnsTuples(queryTree))
return true;
}
return false;
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 9fbf95aea2..7844636e35 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -623,7 +623,6 @@ is_simple_subquery(Query *subquery)
*/
if (!IsA(subquery, Query) ||
subquery->commandType != CMD_SELECT ||
- subquery->resultRelation != 0 ||
subquery->into != NULL)
elog(ERROR, "subquery is bogus");
@@ -686,7 +685,6 @@ is_simple_union_all(Query *subquery)
/* Let's just make sure it's a valid subselect ... */
if (!IsA(subquery, Query) ||
subquery->commandType != CMD_SELECT ||
- subquery->resultRelation != 0 ||
subquery->into != NULL)
elog(ERROR, "subquery is bogus");
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 5ef00feacb..deed9d45a0 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2689,7 +2689,6 @@ inline_function(Oid funcid, Oid result_type, List *args,
*/
if (!IsA(querytree, Query) ||
querytree->commandType != CMD_SELECT ||
- querytree->resultRelation != 0 ||
querytree->into ||
querytree->hasAggs ||
querytree->hasSubLinks ||
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index eb3be85cec..e0713ffe4f 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -3116,6 +3116,15 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt)
/* Shouldn't get any extras, since grammar only allows SelectStmt */
if (extras_before || extras_after)
elog(ERROR, "unexpected extra stuff in cursor statement");
+ if (!IsA(stmt->query, Query) ||
+ ((Query *) stmt->query)->commandType != CMD_SELECT)
+ elog(ERROR, "unexpected non-SELECT command in cursor statement");
+
+ /* But we must explicitly disallow DECLARE CURSOR ... SELECT INTO */
+ if (((Query *) stmt->query)->into)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
+ errmsg("DECLARE CURSOR may not specify INTO")));
return result;
}
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index b224ef2107..772ecba809 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -447,7 +447,7 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
if (query->commandType != CMD_SELECT)
elog(ERROR, "expected SELECT query from subquery in FROM");
- if (query->resultRelation != 0 || query->into != NULL)
+ if (query->into != NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("subquery in FROM may not have SELECT INTO")));
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index c448a8b584..b892882266 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1104,7 +1104,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
elog(ERROR, "bad query in sub-select");
qtree = (Query *) linitial(qtrees);
if (qtree->commandType != CMD_SELECT ||
- qtree->resultRelation != 0)
+ qtree->into != NULL)
elog(ERROR, "bad query in sub-select");
sublink->subselect = (Node *) qtree;
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index a47cd4b1cc..1bb9df7d1d 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -264,7 +264,8 @@ DefineQueryRewrite(RuleStmt *stmt)
* ... the one action must be a SELECT, ...
*/
query = (Query *) linitial(action);
- if (!is_instead || query->commandType != CMD_SELECT)
+ if (!is_instead ||
+ query->commandType != CMD_SELECT || query->into != NULL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("rules on SELECT must have action INSTEAD SELECT")));
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 5348bd5690..430da2a026 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1225,6 +1225,38 @@ UtilityTupleDescriptor(Node *parsetree)
/*
+ * QueryReturnsTuples
+ * Return "true" if this Query will send output to the destination.
+ */
+bool
+QueryReturnsTuples(Query *parsetree)
+{
+ switch (parsetree->commandType)
+ {
+ case CMD_SELECT:
+ /* returns tuples ... unless it's SELECT INTO */
+ if (parsetree->into == NULL)
+ return true;
+ break;
+ case CMD_INSERT:
+ case CMD_UPDATE:
+ case CMD_DELETE:
+ /* the forms with RETURNING return tuples */
+ if (parsetree->returningList)
+ return true;
+ break;
+ case CMD_UTILITY:
+ return UtilityReturnsTuples(parsetree->utilityStmt);
+ case CMD_UNKNOWN:
+ case CMD_NOTHING:
+ /* probably shouldn't get here */
+ break;
+ }
+ return false; /* default */
+}
+
+
+/*
* CreateCommandTag
* utility to get a string representation of the
* command operation, given a raw (un-analyzed) parsetree.
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 44791d95a1..9f0b7289f5 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -89,7 +89,8 @@ typedef struct Query
Node *utilityStmt; /* non-null if this is a non-optimizable
* statement */
- int resultRelation; /* target relation (index into rtable) */
+ int resultRelation; /* rtable index of target relation for
+ * INSERT/UPDATE/DELETE; 0 for SELECT */
RangeVar *into; /* target relation for SELECT INTO */
List *intoOptions; /* options from WITH clause */
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index add9d98a36..adf97c9e18 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -28,6 +28,8 @@ extern const char *CreateCommandTag(Node *parsetree);
extern const char *CreateQueryTag(Query *parsetree);
+extern bool QueryReturnsTuples(Query *parsetree);
+
extern bool QueryIsReadOnly(Query *parsetree);
extern void CheckRelationOwnership(RangeVar *rel, bool noCatalogs);