Adjust handling of command status strings in the presence of rules,

as per recent pghackers discussions.  initdb forced due to change in
fields of stored Query nodes.
This commit is contained in:
Tom Lane 2002-10-14 22:14:35 +00:00
parent ea3728ee5b
commit d508b057ac
10 changed files with 184 additions and 62 deletions

View File

@ -1,4 +1,4 @@
<!-- $Header: /cvsroot/pgsql/doc/src/sgml/rules.sgml,v 1.24 2002/09/21 18:32:53 petere Exp $ -->
<!-- $Header: /cvsroot/pgsql/doc/src/sgml/rules.sgml,v 1.25 2002/10/14 22:14:34 tgl Exp $ -->
<Chapter Id="rules">
<Title>The Rule System</Title>
@ -189,7 +189,10 @@
In INSERT queries the target list describes the new rows that
should go into the result relation. It is the expressions in the VALUES
clause or the ones from the SELECT clause in INSERT ... SELECT.
Missing columns of the result relation will be filled in by the
The first step of the rewrite process adds target list entries
for any columns that were not assigned to by the original query
and have defaults. Any remaining columns (with neither a given
value nor a default) will be filled in by the
planner with a constant NULL expression.
</Para>
@ -197,7 +200,7 @@
In UPDATE queries, the target list describes the new rows that should
replace the old ones. In the rule system, it contains just the
expressions from the SET attribute = expression part of the query.
The planner will add missing columns by inserting expressions that
The planner will handle missing columns by inserting expressions that
copy the values from the old row into the new one. And it will add
the special <acronym>CTID</> entry just as for DELETE too.
</Para>
@ -278,8 +281,8 @@
<Para>
Views in <ProductName>PostgreSQL</ProductName> are implemented
using the rule system. In fact there is absolutely no difference
between a
using the rule system. In fact there is essentially no difference
between
<ProgramListing>
CREATE VIEW myview AS SELECT * FROM mytab;
@ -1133,7 +1136,7 @@ int4ne(NEW.sl_avail, OLD.sl_avail)
<ProgramListing>
INSERT INTO shoelace_log VALUES(
*NEW*.sl_name, *NEW*.sl_avail,
current_user, current_timestamp
current_user, current_timestamp)
FROM shoelace_data *NEW*, shoelace_data *OLD*;
</ProgramListing>
@ -1153,7 +1156,7 @@ INSERT INTO shoelace_log VALUES(
<ProgramListing>
INSERT INTO shoelace_log VALUES(
*NEW*.sl_name, *NEW*.sl_avail,
current_user, current_timestamp
current_user, current_timestamp)
FROM shoelace_data *NEW*, shoelace_data *OLD*,
<emphasis>shoelace_data shoelace_data</emphasis>;
</ProgramListing>
@ -1164,7 +1167,7 @@ INSERT INTO shoelace_log VALUES(
<ProgramListing>
INSERT INTO shoelace_log VALUES(
*NEW*.sl_name, *NEW*.sl_avail,
current_user, current_timestamp
current_user, current_timestamp)
FROM shoelace_data *NEW*, shoelace_data *OLD*,
shoelace_data shoelace_data
<emphasis>WHERE int4ne(*NEW*.sl_avail, *OLD*.sl_avail)</emphasis>;
@ -1174,7 +1177,9 @@ INSERT INTO shoelace_log VALUES(
a WHERE clause either, but the planner and executor will have no
difficulty with it. They need to support this same functionality
anyway for INSERT ... SELECT.
</para>
<para>
In step 3 the original parse tree's qualification is added,
restricting the result set further to only the rows touched
by the original parse tree.
@ -1182,21 +1187,21 @@ INSERT INTO shoelace_log VALUES(
<ProgramListing>
INSERT INTO shoelace_log VALUES(
*NEW*.sl_name, *NEW*.sl_avail,
current_user, current_timestamp
current_user, current_timestamp)
FROM shoelace_data *NEW*, shoelace_data *OLD*,
shoelace_data shoelace_data
WHERE int4ne(*NEW*.sl_avail, *OLD*.sl_avail)
<emphasis>AND bpchareq(shoelace_data.sl_name, 'sl7')</emphasis>;
</ProgramListing>
Step 4 substitutes NEW references by the target list entries from the
original parse tree or with the matching variable references
Step 4 replaces NEW references by the target list entries from the
original parse tree or by the matching variable references
from the result relation.
<ProgramListing>
INSERT INTO shoelace_log VALUES(
<emphasis>shoelace_data.sl_name</emphasis>, <emphasis>6</emphasis>,
current_user, current_timestamp
current_user, current_timestamp)
FROM shoelace_data *NEW*, shoelace_data *OLD*,
shoelace_data shoelace_data
WHERE int4ne(<emphasis>6</emphasis>, *OLD*.sl_avail)
@ -1208,7 +1213,7 @@ INSERT INTO shoelace_log VALUES(
<ProgramListing>
INSERT INTO shoelace_log VALUES(
shoelace_data.sl_name, 6,
current_user, current_timestamp
current_user, current_timestamp)
FROM shoelace_data *NEW*, shoelace_data *OLD*,
shoelace_data shoelace_data
WHERE int4ne(6, <emphasis>shoelace_data.sl_avail</emphasis>)
@ -1222,7 +1227,7 @@ INSERT INTO shoelace_log VALUES(
<ProgramListing>
INSERT INTO shoelace_log VALUES(
shoelace_data.sl_name, 6,
current_user, current_timestamp
current_user, current_timestamp)
FROM shoelace_data
WHERE 6 != shoelace_data.sl_avail
AND shoelace_data.sl_name = 'sl7';
@ -1317,18 +1322,6 @@ CREATE RULE shoe_del_protect AS ON DELETE TO shoe
parse trees will be empty and the whole query will become
nothing because there is nothing left to be optimized or
executed after the rule system is done with it.
<Note>
<Title>Note</Title>
<Para>
This way might irritate frontend applications because
absolutely nothing happened on the database and thus, the
backend will not return anything for the query. Not
even a <symbol>PGRES_EMPTY_QUERY</symbol> will be available in <application>libpq</>.
In <application>psql</application>, nothing happens. This might change in the future.
</Para>
</Note>
</Para>
<Para>
@ -1516,7 +1509,7 @@ UPDATE shoelace_data SET
Again an update rule has been applied and so the wheel
turns on and we are in rewrite round 3. This time rule
<literal>log_shoelace</literal> gets applied what produces the extra
<literal>log_shoelace</literal> gets applied, producing the extra
parse tree
<ProgramListing>
@ -1648,7 +1641,7 @@ sl9 | 0|pink | 35|inch | 88.9
sl10 | 1000|magenta | 40|inch | 101.6
</ProgramListing>
For the 1000 magenta shoelaces we must debt Al before we can
For the 1000 magenta shoelaces we must debit Al before we can
throw 'em away, but that's another problem. The pink entry we delete.
To make it a little harder for <ProductName>PostgreSQL</ProductName>,
we don't delete it directly. Instead we create one more view
@ -1799,6 +1792,56 @@ GRANT SELECT ON phone_number TO secretary;
</Para>
</Sect1>
<Sect1 id="rules-status">
<Title>Rules and Command Status</Title>
<Para>
The <ProductName>PostgreSQL</ProductName> server returns a command
status string, such as <literal>INSERT 149592 1</>, for each
query it receives. This is simple enough when there are no rules
involved, but what happens when the query is rewritten by rules?
</Para>
<Para>
As of <ProductName>PostgreSQL</ProductName> 7.3, rules affect the
command status as follows:
<orderedlist>
<listitem>
<para>
If there is no unconditional INSTEAD rule for the query, then
the originally given query will be executed, and its command
status will be returned as usual. (But note that if there were
any conditional INSTEAD rules, the negation of their qualifications
will have been added to the original query. This may reduce the
number of rows it processes, and if so the reported status will
be affected.)
</para>
</listitem>
<listitem>
<para>
If there is any unconditional INSTEAD rule for the query, then
the original query will not be executed at all. In this case,
the server will return the command status for the last query that
was inserted by an INSTEAD rule (conditional or unconditional)
and is of the same type (INSERT, UPDATE, or DELETE) as the original
query. If no query meeting those requirements is added by any
rule, then the returned command status shows the original query
type and zeroes for the tuple-count and OID fields.
</para>
</listitem>
</orderedlist>
</Para>
<Para>
The programmer can ensure that any desired INSTEAD rule is the one
that sets the command status in the second case, by giving it the
alphabetically last rule name among the active rules, so that it
fires last.
</Para>
</Sect1>
<Sect1 id="rules-triggers">
<Title>Rules versus Triggers</Title>

View File

@ -15,7 +15,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.213 2002/09/22 19:42:51 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.214 2002/10/14 22:14:34 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1778,6 +1778,7 @@ _copyQuery(Query *from)
Query *newnode = makeNode(Query);
newnode->commandType = from->commandType;
newnode->querySource = from->querySource;
Node_Copy(from, newnode, utilityStmt);
newnode->resultRelation = from->resultRelation;
Node_Copy(from, newnode, into);
@ -1785,7 +1786,6 @@ _copyQuery(Query *from)
newnode->isBinary = from->isBinary;
newnode->hasAggs = from->hasAggs;
newnode->hasSubLinks = from->hasSubLinks;
newnode->originalQuery = from->originalQuery;
Node_Copy(from, newnode, rtable);
Node_Copy(from, newnode, jointree);

View File

@ -20,7 +20,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.160 2002/09/22 19:42:51 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.161 2002/10/14 22:14:34 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -573,6 +573,8 @@ _equalQuery(Query *a, Query *b)
{
if (a->commandType != b->commandType)
return false;
if (a->querySource != b->querySource)
return false;
if (!equal(a->utilityStmt, b->utilityStmt))
return false;
if (a->resultRelation != b->resultRelation)
@ -587,7 +589,6 @@ _equalQuery(Query *a, Query *b)
return false;
if (a->hasSubLinks != b->hasSubLinks)
return false;
/* we deliberately ignore originalQuery */
if (!equal(a->rtable, b->rtable))
return false;
if (!equal(a->jointree, b->jointree))

View File

@ -5,7 +5,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.175 2002/09/22 19:42:51 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.176 2002/10/14 22:14:34 tgl Exp $
*
* NOTES
* Every (plan) node in POSTGRES has an associated "out" routine which
@ -229,7 +229,8 @@ _outIndexElem(StringInfo str, IndexElem *node)
static void
_outQuery(StringInfo str, Query *node)
{
appendStringInfo(str, " QUERY :command %d :utility ", node->commandType);
appendStringInfo(str, " QUERY :command %d :source %d :utility ",
(int) node->commandType, (int) node->querySource);
/*
* Hack to work around missing outfuncs routines for a lot of the
@ -299,6 +300,8 @@ _outQuery(StringInfo str, Query *node)
appendStringInfo(str, " :resultRelations ");
_outIntList(str, node->resultRelations);
/* planner-internal fields are not written out */
}
static void

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.134 2002/09/22 19:42:51 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/nodes/readfuncs.c,v 1.135 2002/10/14 22:14:34 tgl Exp $
*
* NOTES
* Most of the read functions for plan nodes are tested. (In fact, they
@ -123,6 +123,10 @@ _readQuery(void)
token = pg_strtok(&length); /* get commandType */
local_node->commandType = atoi(token);
token = pg_strtok(&length); /* skip :source */
token = pg_strtok(&length); /* get querySource */
local_node->querySource = atoi(token);
token = pg_strtok(&length); /* skip :utility */
local_node->utilityStmt = nodeRead(true);
@ -149,9 +153,6 @@ _readQuery(void)
token = pg_strtok(&length); /* get hasSubLinks */
local_node->hasSubLinks = strtobool(token);
/* we always want originalQuery to be false in a read-in query */
local_node->originalQuery = false;
token = pg_strtok(&length); /* skip :rtable */
local_node->rtable = nodeRead(true);
@ -188,6 +189,8 @@ _readQuery(void)
token = pg_strtok(&length); /* skip :resultRelations */
local_node->resultRelations = toIntList(nodeRead(true));
/* planner-internal fields are left zero */
return local_node;
}

View File

@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.250 2002/09/22 00:37:09 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/parser/analyze.c,v 1.251 2002/10/14 22:14:35 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -164,13 +164,13 @@ parse_analyze(Node *parseTree, ParseState *parentParseState)
/*
* Make sure that only the original query is marked original. We have
* to do this explicitly since recursive calls of parse_analyze will
* have set originalQuery in some of the added-on queries.
* have marked some of the added-on queries as "original".
*/
foreach(listscan, result)
{
Query *q = lfirst(listscan);
q->originalQuery = (q == query);
q->querySource = (q == query ? QSRC_ORIGINAL : QSRC_PARSER);
}
pfree(pstate);

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.110 2002/09/18 21:35:22 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.111 2002/10/14 22:14:35 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -942,6 +942,7 @@ fireRules(Query *parsetree,
RewriteRule *rule_lock = (RewriteRule *) lfirst(i);
Node *event_qual;
List *actions;
QuerySource qsrc;
List *r;
/* multiple rule action time */
@ -949,7 +950,18 @@ fireRules(Query *parsetree,
event_qual = rule_lock->qual;
actions = rule_lock->actions;
if (event_qual != NULL && *instead_flag)
/* Determine correct QuerySource value for actions */
if (rule_lock->isInstead)
{
if (event_qual != NULL)
qsrc = QSRC_QUAL_INSTEAD_RULE;
else
qsrc = QSRC_INSTEAD_RULE;
}
else
qsrc = QSRC_NON_INSTEAD_RULE;
if (qsrc == QSRC_QUAL_INSTEAD_RULE)
{
Query *qual_product;
@ -976,6 +988,7 @@ fireRules(Query *parsetree,
*qual_products = makeList1(qual_product);
}
/* Now process the rule's actions and add them to the result list */
foreach(r, actions)
{
Query *rule_action = lfirst(r);
@ -986,6 +999,8 @@ fireRules(Query *parsetree,
rule_action = rewriteRuleAction(parsetree, rule_action,
event_qual, rt_index, event);
rule_action->querySource = qsrc;
results = lappend(results, rule_action);
}
@ -993,9 +1008,10 @@ fireRules(Query *parsetree,
* If this was an unqualified instead rule, throw away an
* eventually saved 'default' parsetree
*/
if (event_qual == NULL && *instead_flag)
if (qsrc == QSRC_INSTEAD_RULE)
*qual_products = NIL;
}
return results;
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.301 2002/10/13 16:55:05 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/tcop/postgres.c,v 1.302 2002/10/14 22:14:35 tgl Exp $
*
* NOTES
* this is the "main" module of the postgres backend and
@ -621,6 +621,8 @@ pg_exec_query_string(StringInfo query_string, /* string to execute */
Node *parsetree = (Node *) lfirst(parsetree_item);
const char *commandTag;
char completionTag[COMPLETION_TAG_BUFSIZE];
CmdType origCmdType;
bool foundOriginalQuery = false;
List *querytree_list,
*querytree_item;
@ -632,6 +634,26 @@ pg_exec_query_string(StringInfo query_string, /* string to execute */
*/
commandTag = CreateCommandTag(parsetree);
switch (nodeTag(parsetree))
{
case T_InsertStmt:
origCmdType = CMD_INSERT;
break;
case T_DeleteStmt:
origCmdType = CMD_DELETE;
break;
case T_UpdateStmt:
origCmdType = CMD_UPDATE;
break;
case T_SelectStmt:
origCmdType = CMD_SELECT;
break;
default:
/* Otherwise, never match commandType */
origCmdType = CMD_UNKNOWN;
break;
}
set_ps_display(commandTag);
BeginCommand(commandTag, dest);
@ -694,6 +716,7 @@ pg_exec_query_string(StringInfo query_string, /* string to execute */
{
Query *querytree = (Query *) lfirst(querytree_item);
bool endTransactionBlock = false;
bool canSetTag;
/* Make sure we are in a transaction command */
if (!xact_started)
@ -708,6 +731,24 @@ pg_exec_query_string(StringInfo query_string, /* string to execute */
*/
CHECK_FOR_INTERRUPTS();
/*
* This query can set the completion tag if it is the original
* query, or if it is an INSTEAD query of the same kind as the
* original and we haven't yet seen the original query.
*/
if (querytree->querySource == QSRC_ORIGINAL)
{
canSetTag = true;
foundOriginalQuery = true;
}
else if (!foundOriginalQuery &&
querytree->commandType == origCmdType &&
(querytree->querySource == QSRC_INSTEAD_RULE ||
querytree->querySource == QSRC_QUAL_INSTEAD_RULE))
canSetTag = true;
else
canSetTag = false;
if (querytree->commandType == CMD_UTILITY)
{
/*
@ -736,7 +777,7 @@ pg_exec_query_string(StringInfo query_string, /* string to execute */
IsA(utilityStmt, VariableResetStmt))
endTransactionBlock = true;
if (querytree->originalQuery)
if (canSetTag)
{
/* utility statement can override default tag string */
ProcessUtility(utilityStmt, dest, completionTag);
@ -785,9 +826,9 @@ pg_exec_query_string(StringInfo query_string, /* string to execute */
{
elog(DEBUG2, "ProcessQuery");
if (querytree->originalQuery || length(querytree_list) == 1)
if (canSetTag)
{
/* original stmt can override default tag string */
/* statement can override default tag string */
ProcessQuery(querytree, plan, dest, completionTag);
commandTag = completionTag;
}
@ -853,17 +894,21 @@ pg_exec_query_string(StringInfo query_string, /* string to execute */
/*
* It is possible that the original query was removed due to a DO
* INSTEAD rewrite rule. In that case we will still have the
* default completion tag, which is fine for most purposes, but it
* INSTEAD rewrite rule. If so, and if we found no INSTEAD query
* matching the command type, we will still have the default
* completion tag. This is fine for most purposes, but it
* may confuse clients if it's INSERT/UPDATE/DELETE. Clients
* expect those tags to have counts after them (cf. ProcessQuery).
*/
if (strcmp(commandTag, "INSERT") == 0)
commandTag = "INSERT 0 0";
else if (strcmp(commandTag, "UPDATE") == 0)
commandTag = "UPDATE 0";
else if (strcmp(commandTag, "DELETE") == 0)
commandTag = "DELETE 0";
if (!foundOriginalQuery)
{
if (strcmp(commandTag, "INSERT") == 0)
commandTag = "INSERT 0 0";
else if (strcmp(commandTag, "UPDATE") == 0)
commandTag = "UPDATE 0";
else if (strcmp(commandTag, "DELETE") == 0)
commandTag = "DELETE 0";
}
/*
* Tell client that we're done with this query. Note we emit
@ -1724,7 +1769,7 @@ PostgresMain(int argc, char *argv[], const char *username)
if (!IsUnderPostmaster)
{
puts("\nPOSTGRES backend interactive interface ");
puts("$Revision: 1.301 $ $Date: 2002/10/13 16:55:05 $\n");
puts("$Revision: 1.302 $ $Date: 2002/10/14 22:14:35 $\n");
}
/*

View File

@ -37,7 +37,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: catversion.h,v 1.160 2002/09/22 19:42:51 tgl Exp $
* $Id: catversion.h,v 1.161 2002/10/14 22:14:35 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 200209221
#define CATALOG_VERSION_NO 200210141
#endif

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: parsenodes.h,v 1.208 2002/09/22 19:42:52 tgl Exp $
* $Id: parsenodes.h,v 1.209 2002/10/14 22:14:35 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -17,6 +17,17 @@
#include "nodes/primnodes.h"
/* Possible sources of a Query */
typedef enum QuerySource
{
QSRC_ORIGINAL, /* original parsetree (explicit query) */
QSRC_PARSER, /* added by parse analysis */
QSRC_INSTEAD_RULE, /* added by unconditional INSTEAD rule */
QSRC_QUAL_INSTEAD_RULE, /* added by conditional INSTEAD rule */
QSRC_NON_INSTEAD_RULE /* added by non-INSTEAD rule */
} QuerySource;
/*****************************************************************************
* Query Tree
*****************************************************************************/
@ -37,6 +48,8 @@ typedef struct Query
CmdType commandType; /* select|insert|update|delete|utility */
QuerySource querySource; /* where did I come from? */
Node *utilityStmt; /* non-null if this is a non-optimizable
* statement */
@ -49,8 +62,6 @@ typedef struct Query
bool hasAggs; /* has aggregates in tlist or havingQual */
bool hasSubLinks; /* has subquery SubLink */
bool originalQuery; /* marks original query through rewriting */
List *rtable; /* list of range table entries */
FromExpr *jointree; /* table join tree (FROM and WHERE
* clauses) */