2001-09-12 03:22:25 +02:00
|
|
|
|
<!-- $Header: /cvsroot/pgsql/doc/src/sgml/rules.sgml,v 1.16 2001/09/12 01:22:25 ishii Exp $ -->
|
2001-07-10 01:50:32 +02:00
|
|
|
|
|
1998-07-29 08:50:04 +02:00
|
|
|
|
<Chapter Id="rules">
|
1998-03-01 09:16:16 +01:00
|
|
|
|
<Title>The <ProductName>Postgres</ProductName> Rule System</Title>
|
|
|
|
|
|
2001-05-13 00:51:36 +02:00
|
|
|
|
<indexterm zone="rules">
|
|
|
|
|
<primary>rules</primary>
|
|
|
|
|
</indexterm>
|
|
|
|
|
|
2000-12-12 06:07:59 +01:00
|
|
|
|
<note>
|
|
|
|
|
<title>Author</title>
|
|
|
|
|
<para>
|
|
|
|
|
Written by Jan Wieck. Updates for 7.1 by Tom Lane.
|
|
|
|
|
</para>
|
|
|
|
|
</note>
|
|
|
|
|
|
1998-03-01 09:16:16 +01:00
|
|
|
|
<Para>
|
1998-10-25 02:29:01 +02:00
|
|
|
|
Production rule systems are conceptually simple, but
|
1998-03-01 09:16:16 +01:00
|
|
|
|
there are many subtle points involved in actually using
|
1998-10-25 02:29:01 +02:00
|
|
|
|
them. Some of these points and
|
|
|
|
|
the theoretical foundations of the <ProductName>Postgres</ProductName>
|
|
|
|
|
rule system can be found in
|
|
|
|
|
[<XRef LinkEnd="STON90b" EndTerm="STON90b">].
|
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
|
|
|
|
Some other database systems define active database rules. These
|
|
|
|
|
are usually stored procedures and triggers and are implemented
|
|
|
|
|
in <ProductName>Postgres</ProductName> as functions and triggers.
|
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
|
|
|
|
The query rewrite rule system (the "rule system" from now on)
|
|
|
|
|
is totally different from stored procedures and triggers.
|
|
|
|
|
It modifies queries to
|
1998-03-01 09:16:16 +01:00
|
|
|
|
take rules into consideration, and then passes the modified
|
2000-12-12 06:07:59 +01:00
|
|
|
|
query to the query planner for planning and execution. It
|
1998-03-01 09:16:16 +01:00
|
|
|
|
is very powerful, and can be used for many things such
|
|
|
|
|
as query language procedures, views, and versions. The
|
|
|
|
|
power of this rule system is discussed in
|
1998-04-28 16:52:46 +02:00
|
|
|
|
[<XRef LinkEnd="ONG90" EndTerm="ONG90">]
|
1998-03-01 09:16:16 +01:00
|
|
|
|
as well as
|
1998-04-28 16:52:46 +02:00
|
|
|
|
[<XRef LinkEnd="STON90b" EndTerm="STON90b">].
|
1998-12-29 03:24:47 +01:00
|
|
|
|
</para>
|
2000-09-29 22:21:34 +02:00
|
|
|
|
<Sect1 id="querytree">
|
2001-09-10 23:58:47 +02:00
|
|
|
|
<Title>What is a Query Tree?</Title>
|
1998-10-25 02:29:01 +02:00
|
|
|
|
|
|
|
|
|
<Para>
|
|
|
|
|
To understand how the rule system works it is necessary to know
|
2000-12-12 06:07:59 +01:00
|
|
|
|
when it is invoked and what its input and results are.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
2000-12-12 06:07:59 +01:00
|
|
|
|
The rule system is located between the query parser and the planner.
|
2001-09-10 23:58:47 +02:00
|
|
|
|
It takes the output of the parser, one query tree, and the rewrite
|
1998-10-25 02:29:01 +02:00
|
|
|
|
rules from the <FileName>pg_rewrite</FileName> catalog, which are
|
2001-09-10 23:58:47 +02:00
|
|
|
|
query trees too with some extra information, and creates zero or many
|
|
|
|
|
query trees as result. So its input and output are always things
|
1998-10-25 02:29:01 +02:00
|
|
|
|
the parser itself could have produced and thus, anything it sees
|
|
|
|
|
is basically representable as an <Acronym>SQL</Acronym> statement.
|
1998-03-01 09:16:16 +01:00
|
|
|
|
</Para>
|
|
|
|
|
|
1998-10-25 02:29:01 +02:00
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
Now what is a query tree? It is an internal representation of an
|
1998-10-25 02:29:01 +02:00
|
|
|
|
<Acronym>SQL</Acronym> statement where the single parts that built
|
2001-09-10 23:58:47 +02:00
|
|
|
|
it are stored separately. These query trees are visible when starting
|
|
|
|
|
the <ProductName>Postgres</ProductName> backend with debug level 4
|
1998-10-25 02:29:01 +02:00
|
|
|
|
and typing queries into the interactive backend interface. The rule
|
|
|
|
|
actions in the <FileName>pg_rewrite</FileName> system catalog are
|
2001-09-10 23:58:47 +02:00
|
|
|
|
also stored as query trees. They are not formatted like the debug
|
1998-10-25 02:29:01 +02:00
|
|
|
|
output, but they contain exactly the same information.
|
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
Reading a query tree requires some experience and it was a hard
|
1998-10-25 02:29:01 +02:00
|
|
|
|
time when I started to work on the rule system. I can remember
|
|
|
|
|
that I was standing at the coffee machine and I saw the cup
|
2001-09-10 23:58:47 +02:00
|
|
|
|
in a target list, water and coffee powder in a range table and all
|
1998-10-25 02:29:01 +02:00
|
|
|
|
the buttons in a qualification expression. Since
|
2001-09-10 23:58:47 +02:00
|
|
|
|
<Acronym>SQL</Acronym> representations of query trees are
|
1998-10-25 02:29:01 +02:00
|
|
|
|
sufficient to understand the rule system, this document will
|
|
|
|
|
not teach how to read them. It might help to learn
|
|
|
|
|
it and the naming conventions are required in the later following
|
|
|
|
|
descriptions.
|
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Sect2>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
<Title>The Parts of a Query tree</Title>
|
1998-10-25 02:29:01 +02:00
|
|
|
|
|
|
|
|
|
<Para>
|
|
|
|
|
When reading the <Acronym>SQL</Acronym> representations of the
|
2001-09-10 23:58:47 +02:00
|
|
|
|
query trees in this document it is necessary to be able to identify
|
|
|
|
|
the parts the statement is broken into when it is in the query tree
|
|
|
|
|
structure. The parts of a query tree are
|
1998-10-25 02:29:01 +02:00
|
|
|
|
|
|
|
|
|
<VariableList>
|
|
|
|
|
<VarListEntry>
|
|
|
|
|
<Term>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
the command type
|
1998-10-25 02:29:01 +02:00
|
|
|
|
</Term>
|
|
|
|
|
<ListItem>
|
|
|
|
|
<Para>
|
|
|
|
|
This is a simple value telling which command
|
2001-09-10 23:58:47 +02:00
|
|
|
|
(SELECT, INSERT, UPDATE, DELETE) produced the parse tree.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
</Para>
|
|
|
|
|
</ListItem>
|
|
|
|
|
</VarListEntry>
|
|
|
|
|
|
|
|
|
|
<VarListEntry>
|
|
|
|
|
<Term>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
the range table
|
1998-10-25 02:29:01 +02:00
|
|
|
|
</Term>
|
|
|
|
|
<ListItem>
|
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
The range table is a list of relations that are used in the query.
|
2000-12-12 06:07:59 +01:00
|
|
|
|
In a SELECT statement these are the relations given after
|
1998-10-25 02:29:01 +02:00
|
|
|
|
the FROM keyword.
|
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
Every range table entry identifies a table or view and tells
|
1998-10-25 02:29:01 +02:00
|
|
|
|
by which name it is called in the other parts of the query.
|
2001-09-10 23:58:47 +02:00
|
|
|
|
In the query tree the range table entries are referenced by
|
1998-10-25 02:29:01 +02:00
|
|
|
|
index rather than by name, so here it doesn't matter if there
|
|
|
|
|
are duplicate names as it would in an <Acronym>SQL</Acronym>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
statement. This can happen after the range tables of rules
|
1998-10-25 02:29:01 +02:00
|
|
|
|
have been merged in. The examples in this document will not have
|
|
|
|
|
this situation.
|
|
|
|
|
</Para>
|
|
|
|
|
</ListItem>
|
|
|
|
|
</VarListEntry>
|
|
|
|
|
|
|
|
|
|
<VarListEntry>
|
|
|
|
|
<Term>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
the result relation
|
1998-10-25 02:29:01 +02:00
|
|
|
|
</Term>
|
|
|
|
|
<ListItem>
|
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
This is an index into the range table that identifies the
|
1998-10-25 02:29:01 +02:00
|
|
|
|
relation where the results of the query go.
|
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
|
|
|
|
SELECT queries
|
|
|
|
|
normally don't have a result relation. The special case
|
|
|
|
|
of a SELECT INTO is mostly identical to a CREATE TABLE,
|
|
|
|
|
INSERT ... SELECT sequence and is not discussed separately
|
|
|
|
|
here.
|
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
On INSERT, UPDATE and DELETE queries the result relation
|
1998-10-25 02:29:01 +02:00
|
|
|
|
is the table (or view!) where the changes take effect.
|
|
|
|
|
</Para>
|
|
|
|
|
</ListItem>
|
|
|
|
|
</VarListEntry>
|
|
|
|
|
|
|
|
|
|
<VarListEntry>
|
|
|
|
|
<Term>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
the target list
|
1998-10-25 02:29:01 +02:00
|
|
|
|
</Term>
|
|
|
|
|
<ListItem>
|
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
The target list is a list of expressions that define the result
|
1998-10-25 02:29:01 +02:00
|
|
|
|
of the query. In the case of a SELECT, the expressions are what
|
|
|
|
|
builds the final output of the query. They are the expressions
|
2000-12-12 06:07:59 +01:00
|
|
|
|
between the SELECT and the FROM keywords. (* is just an
|
|
|
|
|
abbreviation for all the attribute names of a relation. It is
|
|
|
|
|
expanded by the parser into the individual attributes, so the
|
|
|
|
|
rule system never sees it.)
|
1998-10-25 02:29:01 +02:00
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
DELETE queries don't need a target list because they don't
|
2000-12-12 06:07:59 +01:00
|
|
|
|
produce any result. In fact the planner will add a special CTID
|
2001-09-10 23:58:47 +02:00
|
|
|
|
entry to the empty target list. But this is after the rule
|
1998-10-25 02:29:01 +02:00
|
|
|
|
system and will be discussed later. For the rule system the
|
2001-09-10 23:58:47 +02:00
|
|
|
|
target list is empty.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
In INSERT queries the target list describes the new rows that
|
|
|
|
|
should go into the result relation. It is the expressions in the VALUES
|
2000-12-12 06:07:59 +01:00
|
|
|
|
clause or the ones from the SELECT clause in INSERT ... SELECT.
|
2001-09-10 23:58:47 +02:00
|
|
|
|
Missing columns of the result relation will be filled in by the
|
2000-12-12 06:07:59 +01:00
|
|
|
|
planner with a constant NULL expression.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
In UPDATE queries, the target list describes the new rows that should
|
2000-12-12 06:07:59 +01:00
|
|
|
|
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
|
|
|
|
|
copy the values from the old row into the new one. And it will add
|
|
|
|
|
the special CTID entry just as for DELETE too.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
Every entry in the target list contains an expression that can
|
1998-10-25 02:29:01 +02:00
|
|
|
|
be a constant value, a variable pointing to an attribute of one
|
2001-09-10 23:58:47 +02:00
|
|
|
|
of the relations in the range table, a parameter, or an expression
|
1998-10-25 02:29:01 +02:00
|
|
|
|
tree made of function calls, constants, variables, operators etc.
|
|
|
|
|
</Para>
|
|
|
|
|
</ListItem>
|
|
|
|
|
</VarListEntry>
|
|
|
|
|
|
|
|
|
|
<VarListEntry>
|
|
|
|
|
<Term>
|
|
|
|
|
the qualification
|
|
|
|
|
</Term>
|
|
|
|
|
<ListItem>
|
|
|
|
|
<Para>
|
2000-12-12 06:07:59 +01:00
|
|
|
|
The query's qualification is an expression much like one of those
|
2001-09-10 23:58:47 +02:00
|
|
|
|
contained in the target list entries. The result value of this
|
|
|
|
|
expression is a Boolean that tells if the operation
|
1998-10-25 02:29:01 +02:00
|
|
|
|
(INSERT, UPDATE, DELETE or SELECT) for the final result row should be
|
|
|
|
|
executed or not. It is the WHERE clause of an
|
|
|
|
|
<Acronym>SQL</Acronym> statement.
|
|
|
|
|
</Para>
|
|
|
|
|
</ListItem>
|
|
|
|
|
</VarListEntry>
|
|
|
|
|
|
2000-12-12 06:07:59 +01:00
|
|
|
|
<VarListEntry>
|
|
|
|
|
<Term>
|
|
|
|
|
the join tree
|
|
|
|
|
</Term>
|
|
|
|
|
<ListItem>
|
|
|
|
|
<Para>
|
|
|
|
|
The query's join tree shows the structure of the FROM clause.
|
|
|
|
|
For a simple query like SELECT FROM a, b, c the join tree is just
|
|
|
|
|
a list of the FROM items, because we are allowed to join them in
|
|
|
|
|
any order. But when JOIN expressions --- particularly outer joins
|
2001-09-10 23:58:47 +02:00
|
|
|
|
--- are used, we have to join in the order shown by the joins.
|
2000-12-12 06:07:59 +01:00
|
|
|
|
The join tree shows the structure of the JOIN expressions. The
|
|
|
|
|
restrictions associated with particular JOIN clauses (from ON or
|
|
|
|
|
USING expressions) are stored as qualification expressions attached
|
|
|
|
|
to those join tree nodes. It turns out to be convenient to store
|
|
|
|
|
the top-level WHERE expression as a qualification attached to the
|
|
|
|
|
top-level join tree item, too. So really the join tree represents
|
|
|
|
|
both the FROM and WHERE clauses of a SELECT.
|
|
|
|
|
</Para>
|
|
|
|
|
</ListItem>
|
|
|
|
|
</VarListEntry>
|
|
|
|
|
|
1998-10-25 02:29:01 +02:00
|
|
|
|
<VarListEntry>
|
|
|
|
|
<Term>
|
|
|
|
|
the others
|
|
|
|
|
</Term>
|
|
|
|
|
<ListItem>
|
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
The other parts of the query tree like the ORDER BY
|
2000-12-12 06:07:59 +01:00
|
|
|
|
clause aren't of interest here. The rule system
|
1998-10-25 02:29:01 +02:00
|
|
|
|
substitutes entries there while applying rules, but that
|
|
|
|
|
doesn't have much to do with the fundamentals of the rule
|
2000-12-12 06:07:59 +01:00
|
|
|
|
system.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
</Para>
|
|
|
|
|
</ListItem>
|
|
|
|
|
</VarListEntry>
|
|
|
|
|
|
|
|
|
|
</VariableList>
|
1998-12-29 03:24:47 +01:00
|
|
|
|
</para>
|
1998-10-25 02:29:01 +02:00
|
|
|
|
</Sect2>
|
|
|
|
|
</Sect1>
|
|
|
|
|
|
2000-09-29 22:21:34 +02:00
|
|
|
|
<Sect1 id="rules-views">
|
1998-10-25 02:29:01 +02:00
|
|
|
|
<Title>Views and the Rule System</Title>
|
|
|
|
|
|
|
|
|
|
<Sect2>
|
|
|
|
|
<Title>Implementation of Views in <ProductName>Postgres</ProductName></Title>
|
|
|
|
|
|
|
|
|
|
<Para>
|
|
|
|
|
Views in <ProductName>Postgres</ProductName> are implemented
|
|
|
|
|
using the rule system. In fact there is absolutely no difference
|
|
|
|
|
between a
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
CREATE VIEW myview AS SELECT * FROM mytab;
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
compared against the two commands
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
CREATE TABLE myview (<Replaceable>same attribute list as for mytab</Replaceable>);
|
|
|
|
|
CREATE RULE "_RETmyview" AS ON SELECT TO myview DO INSTEAD
|
|
|
|
|
SELECT * FROM mytab;
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
because this is exactly what the CREATE VIEW command does internally.
|
|
|
|
|
This has some side effects. One of them is that
|
|
|
|
|
the information about a view in the <ProductName>Postgres</ProductName>
|
|
|
|
|
system catalogs is exactly the same as it is for a table. So for the
|
2000-12-12 06:07:59 +01:00
|
|
|
|
query parser, there is absolutely no difference between
|
1998-10-25 02:29:01 +02:00
|
|
|
|
a table and a view. They are the same thing - relations. That is the
|
|
|
|
|
important one for now.
|
|
|
|
|
</Para>
|
|
|
|
|
</Sect2>
|
|
|
|
|
|
|
|
|
|
<Sect2>
|
|
|
|
|
<Title>How SELECT Rules Work</Title>
|
1998-04-28 16:52:46 +02:00
|
|
|
|
|
|
|
|
|
<Para>
|
1998-10-25 02:29:01 +02:00
|
|
|
|
Rules ON SELECT are applied to all queries as the
|
|
|
|
|
last step, even if the command
|
|
|
|
|
given is an INSERT, UPDATE or DELETE. And they have different
|
2001-09-10 23:58:47 +02:00
|
|
|
|
semantics from the others in that they modify the parse tree in
|
1998-10-25 02:29:01 +02:00
|
|
|
|
place instead of creating a new one.
|
|
|
|
|
So SELECT rules are described first.
|
1998-04-28 16:52:46 +02:00
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
2000-12-12 06:07:59 +01:00
|
|
|
|
Currently, there can be only one action in an ON SELECT rule, and it must
|
|
|
|
|
be an unconditional SELECT action that is INSTEAD. This restriction was
|
|
|
|
|
required to make rules safe enough to open them for ordinary users and
|
1998-10-25 02:29:01 +02:00
|
|
|
|
it restricts rules ON SELECT to real view rules.
|
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
2000-12-12 06:07:59 +01:00
|
|
|
|
The examples for this document are two join views that do some calculations
|
1998-10-25 02:29:01 +02:00
|
|
|
|
and some more views using them in turn.
|
|
|
|
|
One of the two first views is customized later by adding rules for
|
|
|
|
|
INSERT, UPDATE and DELETE operations so that the final result will
|
|
|
|
|
be a view that behaves like a real table with some magic functionality.
|
|
|
|
|
It is not such a simple example to start from and this makes things
|
|
|
|
|
harder to get into. But it's better to have one example that covers
|
|
|
|
|
all the points discussed step by step rather than having many
|
|
|
|
|
different ones that might mix up in mind.
|
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
The database needed to play with the examples is named <literal>al_bundy</literal>.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
You'll see soon why this is the database name. And it needs the
|
2001-09-10 23:58:47 +02:00
|
|
|
|
procedural language <application>PL/pgSQL</> installed, because
|
1998-10-25 02:29:01 +02:00
|
|
|
|
we need a little min() function returning the lower of 2
|
|
|
|
|
integer values. We create that as
|
|
|
|
|
|
1998-04-28 16:52:46 +02:00
|
|
|
|
<ProgramListing>
|
1998-10-25 02:29:01 +02:00
|
|
|
|
CREATE FUNCTION min(integer, integer) RETURNS integer AS
|
|
|
|
|
'BEGIN
|
|
|
|
|
IF $1 < $2 THEN
|
|
|
|
|
RETURN $1;
|
|
|
|
|
END IF;
|
|
|
|
|
RETURN $2;
|
|
|
|
|
END;'
|
|
|
|
|
LANGUAGE 'plpgsql';
|
1998-04-28 16:52:46 +02:00
|
|
|
|
</ProgramListing>
|
1998-10-25 02:29:01 +02:00
|
|
|
|
</Para>
|
1998-04-28 16:52:46 +02:00
|
|
|
|
|
|
|
|
|
<Para>
|
2000-12-12 06:07:59 +01:00
|
|
|
|
The real tables we need in the first two rule system descriptions
|
1998-10-25 02:29:01 +02:00
|
|
|
|
are these:
|
1998-04-28 16:52:46 +02:00
|
|
|
|
|
1998-10-25 02:29:01 +02:00
|
|
|
|
<ProgramListing>
|
|
|
|
|
CREATE TABLE shoe_data (
|
|
|
|
|
shoename char(10), -- primary key
|
|
|
|
|
sh_avail integer, -- available # of pairs
|
|
|
|
|
slcolor char(10), -- preferred shoelace color
|
|
|
|
|
slminlen float, -- miminum shoelace length
|
|
|
|
|
slmaxlen float, -- maximum shoelace length
|
|
|
|
|
slunit char(8) -- length unit
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
CREATE TABLE shoelace_data (
|
|
|
|
|
sl_name char(10), -- primary key
|
|
|
|
|
sl_avail integer, -- available # of pairs
|
|
|
|
|
sl_color char(10), -- shoelace color
|
|
|
|
|
sl_len float, -- shoelace length
|
|
|
|
|
sl_unit char(8) -- length unit
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
CREATE TABLE unit (
|
|
|
|
|
un_name char(8), -- the primary key
|
|
|
|
|
un_fact float -- factor to transform to cm
|
|
|
|
|
);
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
I think most of us wear shoes and can realize that this is
|
|
|
|
|
really useful data. Well there are shoes out in the world
|
|
|
|
|
that don't require shoelaces, but this doesn't make Al's
|
|
|
|
|
life easier and so we ignore it.
|
|
|
|
|
</Para>
|
1998-04-28 16:52:46 +02:00
|
|
|
|
|
|
|
|
|
<Para>
|
1998-10-25 02:29:01 +02:00
|
|
|
|
The views are created as
|
1998-04-28 16:52:46 +02:00
|
|
|
|
|
1998-10-25 02:29:01 +02:00
|
|
|
|
<ProgramListing>
|
|
|
|
|
CREATE VIEW shoe AS
|
|
|
|
|
SELECT sh.shoename,
|
|
|
|
|
sh.sh_avail,
|
|
|
|
|
sh.slcolor,
|
|
|
|
|
sh.slminlen,
|
|
|
|
|
sh.slminlen * un.un_fact AS slminlen_cm,
|
|
|
|
|
sh.slmaxlen,
|
|
|
|
|
sh.slmaxlen * un.un_fact AS slmaxlen_cm,
|
|
|
|
|
sh.slunit
|
|
|
|
|
FROM shoe_data sh, unit un
|
|
|
|
|
WHERE sh.slunit = un.un_name;
|
|
|
|
|
|
|
|
|
|
CREATE VIEW shoelace AS
|
|
|
|
|
SELECT s.sl_name,
|
|
|
|
|
s.sl_avail,
|
|
|
|
|
s.sl_color,
|
|
|
|
|
s.sl_len,
|
|
|
|
|
s.sl_unit,
|
|
|
|
|
s.sl_len * u.un_fact AS sl_len_cm
|
|
|
|
|
FROM shoelace_data s, unit u
|
|
|
|
|
WHERE s.sl_unit = u.un_name;
|
|
|
|
|
|
|
|
|
|
CREATE VIEW shoe_ready AS
|
|
|
|
|
SELECT rsh.shoename,
|
|
|
|
|
rsh.sh_avail,
|
|
|
|
|
rsl.sl_name,
|
|
|
|
|
rsl.sl_avail,
|
|
|
|
|
min(rsh.sh_avail, rsl.sl_avail) AS total_avail
|
|
|
|
|
FROM shoe rsh, shoelace rsl
|
|
|
|
|
WHERE rsl.sl_color = rsh.slcolor
|
|
|
|
|
AND rsl.sl_len_cm >= rsh.slminlen_cm
|
|
|
|
|
AND rsl.sl_len_cm <= rsh.slmaxlen_cm;
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
The CREATE VIEW command for the <Filename>shoelace</Filename>
|
|
|
|
|
view (which is the simplest one we have)
|
|
|
|
|
will create a relation shoelace and an entry
|
|
|
|
|
in <FileName>pg_rewrite</FileName>
|
|
|
|
|
that tells that there is a rewrite rule that must be applied
|
2001-09-10 23:58:47 +02:00
|
|
|
|
whenever the relation shoelace is referenced in a query's range table.
|
2000-12-12 06:07:59 +01:00
|
|
|
|
The rule has no rule qualification (discussed later, with the
|
|
|
|
|
non SELECT rules, since SELECT rules currently cannot have them) and
|
1998-10-25 02:29:01 +02:00
|
|
|
|
it is INSTEAD. Note that rule qualifications are not the same as
|
2000-12-12 06:07:59 +01:00
|
|
|
|
query qualifications! The rule's action has a query qualification.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
The rule's action is one query tree that is a copy of the
|
1998-10-25 02:29:01 +02:00
|
|
|
|
SELECT statement in the view creation command.
|
|
|
|
|
|
|
|
|
|
<Note>
|
|
|
|
|
<Title>Note</Title>
|
|
|
|
|
<Para>
|
|
|
|
|
The two extra range
|
|
|
|
|
table entries for NEW and OLD (named *NEW* and *CURRENT* for
|
2001-09-10 23:58:47 +02:00
|
|
|
|
historical reasons in the printed query tree) you can see in
|
1998-10-25 02:29:01 +02:00
|
|
|
|
the <Filename>pg_rewrite</Filename> entry aren't of interest
|
|
|
|
|
for SELECT rules.
|
|
|
|
|
</Para>
|
|
|
|
|
</Note>
|
|
|
|
|
|
|
|
|
|
Now we populate <Filename>unit</Filename>, <Filename>shoe_data</Filename>
|
|
|
|
|
and <Filename>shoelace_data</Filename> and Al types the first
|
|
|
|
|
SELECT in his life:
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
al_bundy=> INSERT INTO unit VALUES ('cm', 1.0);
|
|
|
|
|
al_bundy=> INSERT INTO unit VALUES ('m', 100.0);
|
|
|
|
|
al_bundy=> INSERT INTO unit VALUES ('inch', 2.54);
|
|
|
|
|
al_bundy=>
|
|
|
|
|
al_bundy=> INSERT INTO shoe_data VALUES
|
|
|
|
|
al_bundy-> ('sh1', 2, 'black', 70.0, 90.0, 'cm');
|
|
|
|
|
al_bundy=> INSERT INTO shoe_data VALUES
|
|
|
|
|
al_bundy-> ('sh2', 0, 'black', 30.0, 40.0, 'inch');
|
|
|
|
|
al_bundy=> INSERT INTO shoe_data VALUES
|
|
|
|
|
al_bundy-> ('sh3', 4, 'brown', 50.0, 65.0, 'cm');
|
|
|
|
|
al_bundy=> INSERT INTO shoe_data VALUES
|
|
|
|
|
al_bundy-> ('sh4', 3, 'brown', 40.0, 50.0, 'inch');
|
|
|
|
|
al_bundy=>
|
|
|
|
|
al_bundy=> INSERT INTO shoelace_data VALUES
|
|
|
|
|
al_bundy-> ('sl1', 5, 'black', 80.0, 'cm');
|
|
|
|
|
al_bundy=> INSERT INTO shoelace_data VALUES
|
|
|
|
|
al_bundy-> ('sl2', 6, 'black', 100.0, 'cm');
|
|
|
|
|
al_bundy=> INSERT INTO shoelace_data VALUES
|
|
|
|
|
al_bundy-> ('sl3', 0, 'black', 35.0 , 'inch');
|
|
|
|
|
al_bundy=> INSERT INTO shoelace_data VALUES
|
|
|
|
|
al_bundy-> ('sl4', 8, 'black', 40.0 , 'inch');
|
|
|
|
|
al_bundy=> INSERT INTO shoelace_data VALUES
|
|
|
|
|
al_bundy-> ('sl5', 4, 'brown', 1.0 , 'm');
|
|
|
|
|
al_bundy=> INSERT INTO shoelace_data VALUES
|
|
|
|
|
al_bundy-> ('sl6', 0, 'brown', 0.9 , 'm');
|
|
|
|
|
al_bundy=> INSERT INTO shoelace_data VALUES
|
|
|
|
|
al_bundy-> ('sl7', 7, 'brown', 60 , 'cm');
|
|
|
|
|
al_bundy=> INSERT INTO shoelace_data VALUES
|
|
|
|
|
al_bundy-> ('sl8', 1, 'brown', 40 , 'inch');
|
|
|
|
|
al_bundy=>
|
|
|
|
|
al_bundy=> SELECT * FROM shoelace;
|
|
|
|
|
sl_name |sl_avail|sl_color |sl_len|sl_unit |sl_len_cm
|
|
|
|
|
----------+--------+----------+------+--------+---------
|
|
|
|
|
sl1 | 5|black | 80|cm | 80
|
|
|
|
|
sl2 | 6|black | 100|cm | 100
|
|
|
|
|
sl7 | 7|brown | 60|cm | 60
|
|
|
|
|
sl3 | 0|black | 35|inch | 88.9
|
|
|
|
|
sl4 | 8|black | 40|inch | 101.6
|
|
|
|
|
sl8 | 1|brown | 40|inch | 101.6
|
|
|
|
|
sl5 | 4|brown | 1|m | 100
|
|
|
|
|
sl6 | 0|brown | 0.9|m | 90
|
|
|
|
|
(8 rows)
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
It's the simplest SELECT Al can do on our views, so we take this
|
|
|
|
|
to explain the basics of view rules.
|
|
|
|
|
The 'SELECT * FROM shoelace' was interpreted by the parser and
|
|
|
|
|
produced the parsetree
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
SELECT shoelace.sl_name, shoelace.sl_avail,
|
|
|
|
|
shoelace.sl_color, shoelace.sl_len,
|
|
|
|
|
shoelace.sl_unit, shoelace.sl_len_cm
|
|
|
|
|
FROM shoelace shoelace;
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
and this is given to the rule system. The rule system walks through the
|
2001-09-10 23:58:47 +02:00
|
|
|
|
range table and checks if there are rules in <Filename>pg_rewrite</Filename>
|
|
|
|
|
for any relation. When processing the range table entry for
|
1998-10-25 02:29:01 +02:00
|
|
|
|
<Filename>shoelace</Filename> (the only one up to now) it finds the
|
2001-09-10 23:58:47 +02:00
|
|
|
|
rule <literal>_RETshoelace</literal> with the parse tree
|
1998-10-25 02:29:01 +02:00
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
<FirstTerm>SELECT s.sl_name, s.sl_avail,
|
|
|
|
|
s.sl_color, s.sl_len, s.sl_unit,
|
|
|
|
|
float8mul(s.sl_len, u.un_fact) AS sl_len_cm
|
|
|
|
|
FROM shoelace *OLD*, shoelace *NEW*,
|
|
|
|
|
shoelace_data s, unit u
|
|
|
|
|
WHERE bpchareq(s.sl_unit, u.un_name);</FirstTerm>
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
Note that the parser changed the calculation and qualification into
|
|
|
|
|
calls to the appropriate functions. But
|
|
|
|
|
in fact this changes nothing.
|
2000-12-12 06:07:59 +01:00
|
|
|
|
</Para>
|
1998-10-25 02:29:01 +02:00
|
|
|
|
|
2000-12-12 06:07:59 +01:00
|
|
|
|
<Para>
|
|
|
|
|
To expand the view, the rewriter simply creates a subselect rangetable
|
|
|
|
|
entry containing the rule's action parsetree, and substitutes this
|
2001-09-10 23:58:47 +02:00
|
|
|
|
range table entry for the original one that referenced the view. The
|
|
|
|
|
resulting rewritten parse tree is almost the same as if Al had typed
|
1998-10-25 02:29:01 +02:00
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
SELECT shoelace.sl_name, shoelace.sl_avail,
|
|
|
|
|
shoelace.sl_color, shoelace.sl_len,
|
|
|
|
|
shoelace.sl_unit, shoelace.sl_len_cm
|
2000-12-12 06:07:59 +01:00
|
|
|
|
FROM (SELECT s.sl_name,
|
|
|
|
|
s.sl_avail,
|
|
|
|
|
s.sl_color,
|
|
|
|
|
s.sl_len,
|
|
|
|
|
s.sl_unit,
|
|
|
|
|
s.sl_len * u.un_fact AS sl_len_cm
|
|
|
|
|
FROM shoelace_data s, unit u
|
|
|
|
|
WHERE s.sl_unit = u.un_name) shoelace;
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
2001-09-10 23:58:47 +02:00
|
|
|
|
There is one difference however: the sub-query's range table has two
|
2000-12-12 06:07:59 +01:00
|
|
|
|
extra entries shoelace *OLD*, shoelace *NEW*. These entries don't
|
|
|
|
|
participate directly in the query, since they aren't referenced by
|
2001-09-10 23:58:47 +02:00
|
|
|
|
the sub-query's join tree or target list. The rewriter uses them
|
2000-12-12 06:07:59 +01:00
|
|
|
|
to store the access permission check info that was originally present
|
|
|
|
|
in the rangetable entry that referenced the view. In this way, the
|
|
|
|
|
executor will still check that the user has proper permissions to access
|
|
|
|
|
the view, even though there's no direct use of the view in the rewritten
|
|
|
|
|
query.
|
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
|
|
|
|
That was the first rule applied. The rule system will continue checking
|
|
|
|
|
the remaining rangetable entries in the top query (in this example there
|
|
|
|
|
are no more), and it will recursively check the rangetable entries in
|
|
|
|
|
the added sub-query to see if any of them reference views. (But it
|
|
|
|
|
won't expand *OLD* or *NEW* --- otherwise we'd have infinite recursion!)
|
|
|
|
|
In this example, there are no rewrite rules for shoelace_data or unit,
|
|
|
|
|
so rewriting is complete and the above is the final result given to
|
|
|
|
|
the planner.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
|
|
|
|
Now we face Al with the problem that the Blues Brothers appear
|
|
|
|
|
in his shop and
|
|
|
|
|
want to buy some new shoes, and as the Blues Brothers are,
|
|
|
|
|
they want to wear the same shoes. And they want to wear them
|
|
|
|
|
immediately, so they need shoelaces too.
|
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
|
|
|
|
Al needs to know for which shoes currently in the store
|
|
|
|
|
he has the matching shoelaces (color and size) and where the
|
|
|
|
|
total number of exactly matching pairs is greater or equal to two.
|
2000-12-12 06:07:59 +01:00
|
|
|
|
We teach him what to do and he asks his database:
|
1998-10-25 02:29:01 +02:00
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
al_bundy=> SELECT * FROM shoe_ready WHERE total_avail >= 2;
|
|
|
|
|
shoename |sh_avail|sl_name |sl_avail|total_avail
|
|
|
|
|
----------+--------+----------+--------+-----------
|
|
|
|
|
sh1 | 2|sl1 | 5| 2
|
|
|
|
|
sh3 | 4|sl7 | 7| 4
|
|
|
|
|
(2 rows)
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
Al is a shoe guru and so he knows that only shoes of type sh1
|
|
|
|
|
would fit (shoelace sl7 is brown and shoes that need brown shoelaces
|
|
|
|
|
aren't shoes the Blues Brothers would ever wear).
|
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
The output of the parser this time is the parse tree
|
1998-10-25 02:29:01 +02:00
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
SELECT shoe_ready.shoename, shoe_ready.sh_avail,
|
|
|
|
|
shoe_ready.sl_name, shoe_ready.sl_avail,
|
|
|
|
|
shoe_ready.total_avail
|
|
|
|
|
FROM shoe_ready shoe_ready
|
|
|
|
|
WHERE int4ge(shoe_ready.total_avail, 2);
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
2000-12-12 06:07:59 +01:00
|
|
|
|
The first rule applied will be the one for the
|
|
|
|
|
<Filename>shoe_ready</Filename> view and it results in the
|
2001-09-10 23:58:47 +02:00
|
|
|
|
parse tree
|
1998-10-25 02:29:01 +02:00
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
2000-12-12 06:07:59 +01:00
|
|
|
|
SELECT shoe_ready.shoename, shoe_ready.sh_avail,
|
|
|
|
|
shoe_ready.sl_name, shoe_ready.sl_avail,
|
|
|
|
|
shoe_ready.total_avail
|
|
|
|
|
FROM (SELECT rsh.shoename,
|
|
|
|
|
rsh.sh_avail,
|
|
|
|
|
rsl.sl_name,
|
|
|
|
|
rsl.sl_avail,
|
|
|
|
|
min(rsh.sh_avail, rsl.sl_avail) AS total_avail
|
|
|
|
|
FROM shoe rsh, shoelace rsl
|
|
|
|
|
WHERE rsl.sl_color = rsh.slcolor
|
|
|
|
|
AND rsl.sl_len_cm >= rsh.slminlen_cm
|
|
|
|
|
AND rsl.sl_len_cm <= rsh.slmaxlen_cm) shoe_ready
|
|
|
|
|
WHERE int4ge(shoe_ready.total_avail, 2);
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
Similarly, the rules for <Filename>shoe</Filename> and
|
2001-09-10 23:58:47 +02:00
|
|
|
|
<Filename>shoelace</Filename> are substituted into the range table of
|
|
|
|
|
the sub-query, leading to a three-level final query tree:
|
2000-12-12 06:07:59 +01:00
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
SELECT shoe_ready.shoename, shoe_ready.sh_avail,
|
|
|
|
|
shoe_ready.sl_name, shoe_ready.sl_avail,
|
|
|
|
|
shoe_ready.total_avail
|
|
|
|
|
FROM (SELECT rsh.shoename,
|
|
|
|
|
rsh.sh_avail,
|
|
|
|
|
rsl.sl_name,
|
|
|
|
|
rsl.sl_avail,
|
|
|
|
|
min(rsh.sh_avail, rsl.sl_avail) AS total_avail
|
|
|
|
|
FROM (SELECT sh.shoename,
|
|
|
|
|
sh.sh_avail,
|
|
|
|
|
sh.slcolor,
|
|
|
|
|
sh.slminlen,
|
|
|
|
|
sh.slminlen * un.un_fact AS slminlen_cm,
|
|
|
|
|
sh.slmaxlen,
|
|
|
|
|
sh.slmaxlen * un.un_fact AS slmaxlen_cm,
|
|
|
|
|
sh.slunit
|
|
|
|
|
FROM shoe_data sh, unit un
|
|
|
|
|
WHERE sh.slunit = un.un_name) rsh,
|
|
|
|
|
(SELECT s.sl_name,
|
|
|
|
|
s.sl_avail,
|
|
|
|
|
s.sl_color,
|
|
|
|
|
s.sl_len,
|
|
|
|
|
s.sl_unit,
|
|
|
|
|
s.sl_len * u.un_fact AS sl_len_cm
|
|
|
|
|
FROM shoelace_data s, unit u
|
|
|
|
|
WHERE s.sl_unit = u.un_name) rsl
|
|
|
|
|
WHERE rsl.sl_color = rsh.slcolor
|
|
|
|
|
AND rsl.sl_len_cm >= rsh.slminlen_cm
|
|
|
|
|
AND rsl.sl_len_cm <= rsh.slmaxlen_cm) shoe_ready
|
|
|
|
|
WHERE int4ge(shoe_ready.total_avail, 2);
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
It turns out that the planner will collapse this tree into a two-level
|
2001-09-10 23:58:47 +02:00
|
|
|
|
query tree: the bottommost selects will be "pulled up" into the middle
|
2000-12-12 06:07:59 +01:00
|
|
|
|
select since there's no need to process them separately. But the
|
|
|
|
|
middle select will remain separate from the top, because it contains
|
|
|
|
|
aggregate functions. If we pulled those up it would change the behavior
|
|
|
|
|
of the topmost select, which we don't want. However, collapsing the
|
|
|
|
|
query tree is an optimization that the rewrite system doesn't
|
|
|
|
|
have to concern itself with.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
|
|
|
|
|
<Note>
|
|
|
|
|
<Title>Note</Title>
|
|
|
|
|
<Para>
|
|
|
|
|
There is currently no recursion stopping mechanism for view
|
2000-12-12 06:07:59 +01:00
|
|
|
|
rules in the rule system (only for the other kinds of rules).
|
1998-10-25 02:29:01 +02:00
|
|
|
|
This doesn't hurt much, because the only way to push this
|
|
|
|
|
into an endless loop (blowing up the
|
|
|
|
|
backend until it reaches the memory limit)
|
|
|
|
|
is to create tables and then setup the
|
|
|
|
|
view rules by hand with CREATE RULE in such a way, that
|
|
|
|
|
one selects from the other that selects from the one.
|
|
|
|
|
This could never happen if CREATE VIEW is used because
|
2000-12-12 06:07:59 +01:00
|
|
|
|
for the first CREATE VIEW, the second relation does not exist
|
1998-10-25 02:29:01 +02:00
|
|
|
|
and thus the first view cannot select from the second.
|
|
|
|
|
</Para>
|
|
|
|
|
</Note>
|
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
</Sect2>
|
|
|
|
|
|
|
|
|
|
<Sect2>
|
|
|
|
|
<Title>View Rules in Non-SELECT Statements</Title>
|
1998-04-28 16:52:46 +02:00
|
|
|
|
|
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
Two details of the parse tree aren't touched in the description of
|
|
|
|
|
view rules above. These are the command type and the result relation.
|
2000-12-12 06:07:59 +01:00
|
|
|
|
In fact, view rules don't need this information.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
</Para>
|
1998-04-28 16:52:46 +02:00
|
|
|
|
|
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
There are only a few differences between a parse tree for a SELECT
|
|
|
|
|
and one for any other command. Obviously they have another command type
|
|
|
|
|
and this time the result relation points to the range table entry where
|
2000-12-12 06:07:59 +01:00
|
|
|
|
the result should go. Everything else is absolutely the same.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
So having two tables t1 and t2 with attributes
|
2001-09-10 23:58:47 +02:00
|
|
|
|
a and b, the parse trees for the two statements
|
1998-10-25 02:29:01 +02:00
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
SELECT t2.b FROM t1, t2 WHERE t1.a = t2.a;
|
|
|
|
|
|
|
|
|
|
UPDATE t1 SET b = t2.b WHERE t1.a = t2.a;
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
are nearly identical.
|
|
|
|
|
|
|
|
|
|
<ItemizedList>
|
|
|
|
|
<ListItem>
|
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
The range tables contain entries for the tables t1 and t2.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
</Para>
|
|
|
|
|
</ListItem>
|
|
|
|
|
|
|
|
|
|
<ListItem>
|
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
The target lists contain one variable that points to attribute
|
|
|
|
|
b of the range table entry for table t2.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
</Para>
|
|
|
|
|
</ListItem>
|
|
|
|
|
|
|
|
|
|
<ListItem>
|
|
|
|
|
<Para>
|
|
|
|
|
The qualification expressions compare the attributes a of both
|
|
|
|
|
ranges for equality.
|
|
|
|
|
</Para>
|
|
|
|
|
</ListItem>
|
2000-12-12 06:07:59 +01:00
|
|
|
|
|
|
|
|
|
<ListItem>
|
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
The join trees show a simple join between t1 and t2.
|
2000-12-12 06:07:59 +01:00
|
|
|
|
</Para>
|
|
|
|
|
</ListItem>
|
1998-10-25 02:29:01 +02:00
|
|
|
|
</ItemizedList>
|
|
|
|
|
|
2001-09-10 23:58:47 +02:00
|
|
|
|
The consequence is, that both parse trees result in similar execution
|
1998-10-25 02:29:01 +02:00
|
|
|
|
plans. They are both joins over the two tables. For the UPDATE
|
2001-09-10 23:58:47 +02:00
|
|
|
|
the missing columns from t1 are added to the target list by the planner
|
|
|
|
|
and the final parse tree will read as
|
1998-10-25 02:29:01 +02:00
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
UPDATE t1 SET a = t1.a, b = t2.b WHERE t1.a = t2.a;
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
and thus the executor run over the join will produce exactly the
|
|
|
|
|
same result set as a
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
SELECT t1.a, t2.b FROM t1, t2 WHERE t1.a = t2.a;
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
will do. But there is a little problem in UPDATE. The executor does
|
|
|
|
|
not care what the results from the join it is doing are meant
|
|
|
|
|
for. It just produces a result set of rows. The difference that one
|
|
|
|
|
is a SELECT command and the other is an UPDATE is handled in the
|
|
|
|
|
caller of the executor. The caller still knows (looking at the
|
2001-09-10 23:58:47 +02:00
|
|
|
|
parse tree) that this is an UPDATE, and he knows that this result
|
2000-12-12 06:07:59 +01:00
|
|
|
|
should go into table t1. But which of the rows that are there
|
|
|
|
|
has to be replaced by the new row?
|
1998-04-28 16:52:46 +02:00
|
|
|
|
</Para>
|
|
|
|
|
|
1998-10-25 02:29:01 +02:00
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
To resolve this problem, another entry is added to the target list
|
2000-12-12 06:07:59 +01:00
|
|
|
|
in UPDATE (and also in DELETE) statements: the current tuple ID (ctid).
|
|
|
|
|
This is a system attribute containing the file
|
|
|
|
|
block number and position in the block for the row. Knowing the table,
|
|
|
|
|
the ctid can be used to retrieve the original t1 row to be updated.
|
2001-09-10 23:58:47 +02:00
|
|
|
|
After adding the ctid to the target list, the query actually looks like
|
1998-10-25 02:29:01 +02:00
|
|
|
|
|
1998-04-28 16:52:46 +02:00
|
|
|
|
<ProgramListing>
|
1998-10-25 02:29:01 +02:00
|
|
|
|
SELECT t1.a, t2.b, t1.ctid FROM t1, t2 WHERE t1.a = t2.a;
|
1998-04-28 16:52:46 +02:00
|
|
|
|
</ProgramListing>
|
1998-10-25 02:29:01 +02:00
|
|
|
|
|
|
|
|
|
Now another detail of <ProductName>Postgres</ProductName> enters the
|
|
|
|
|
stage. At this moment, table rows aren't overwritten and this is why
|
|
|
|
|
ABORT TRANSACTION is fast. In an UPDATE, the new result row is inserted
|
|
|
|
|
into the table (after stripping ctid) and in the tuple header of the row
|
|
|
|
|
that ctid pointed to the cmax and xmax entries are set to the current
|
|
|
|
|
command counter and current transaction ID. Thus the old row is hidden
|
2001-09-10 23:58:47 +02:00
|
|
|
|
and after the transaction committed the vacuum cleaner can really move
|
1998-10-25 02:29:01 +02:00
|
|
|
|
it out.
|
|
|
|
|
</Para>
|
1998-04-28 16:52:46 +02:00
|
|
|
|
|
|
|
|
|
<Para>
|
2000-12-12 06:07:59 +01:00
|
|
|
|
Knowing all that, we can simply apply view rules in absolutely
|
1998-10-25 02:29:01 +02:00
|
|
|
|
the same way to any command. There is no difference.
|
|
|
|
|
</Para>
|
|
|
|
|
</Sect2>
|
|
|
|
|
|
|
|
|
|
<Sect2>
|
|
|
|
|
<Title>The Power of Views in <ProductName>Postgres</ProductName></Title>
|
|
|
|
|
|
|
|
|
|
<Para>
|
|
|
|
|
The above demonstrates how the rule system incorporates
|
2001-09-10 23:58:47 +02:00
|
|
|
|
view definitions into the original parse tree. In the second example
|
|
|
|
|
a simple SELECT from one view created a final parse tree that is
|
1998-10-25 02:29:01 +02:00
|
|
|
|
a join of 4 tables (unit is used twice with different names).
|
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Sect3>
|
|
|
|
|
<Title>Benefits</Title>
|
1998-04-28 16:52:46 +02:00
|
|
|
|
|
|
|
|
|
<Para>
|
1998-10-25 02:29:01 +02:00
|
|
|
|
The benefit of implementing views with the rule system is,
|
2000-12-12 06:07:59 +01:00
|
|
|
|
that the planner has all
|
1998-10-25 02:29:01 +02:00
|
|
|
|
the information about which tables have to be scanned plus the
|
|
|
|
|
relationships between these tables plus the restrictive
|
|
|
|
|
qualifications from the views plus the qualifications from
|
|
|
|
|
the original query
|
2001-09-10 23:58:47 +02:00
|
|
|
|
in one single parse tree. And this is still the situation
|
1998-10-25 02:29:01 +02:00
|
|
|
|
when the original query is already a join over views.
|
2000-12-12 06:07:59 +01:00
|
|
|
|
Now the planner has to decide which is
|
1998-10-25 02:29:01 +02:00
|
|
|
|
the best path to execute the query. The more information
|
2000-12-12 06:07:59 +01:00
|
|
|
|
the planner has, the better this decision can be. And
|
1998-10-25 02:29:01 +02:00
|
|
|
|
the rule system as implemented in <ProductName>Postgres</ProductName>
|
|
|
|
|
ensures, that this is all information available about the query
|
|
|
|
|
up to now.
|
|
|
|
|
</Para>
|
|
|
|
|
</Sect3>
|
|
|
|
|
</Sect2>
|
|
|
|
|
|
|
|
|
|
<Sect2>
|
2000-12-12 06:07:59 +01:00
|
|
|
|
<Title>What about updating a view?</Title>
|
1998-04-28 16:52:46 +02:00
|
|
|
|
|
|
|
|
|
<Para>
|
2000-12-12 06:07:59 +01:00
|
|
|
|
What happens if a view is named as the target relation for an INSERT,
|
|
|
|
|
UPDATE, or DELETE? After doing the substitutions described above,
|
2001-09-10 23:58:47 +02:00
|
|
|
|
we will have a query tree in which the result relation points at a
|
|
|
|
|
subquery range table entry. This will not work, so the rewriter throws
|
2000-12-12 06:07:59 +01:00
|
|
|
|
an error if it sees it has produced such a thing.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
</Para>
|
1998-04-28 16:52:46 +02:00
|
|
|
|
|
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
To change this we can define rules that modify the behavior
|
1998-10-25 02:29:01 +02:00
|
|
|
|
of non-SELECT queries. This is the topic of the next section.
|
|
|
|
|
</Para>
|
|
|
|
|
</Sect2>
|
|
|
|
|
|
|
|
|
|
</Sect1>
|
1998-04-28 16:52:46 +02:00
|
|
|
|
|
2000-09-29 22:21:34 +02:00
|
|
|
|
<Sect1 id="rules-insert">
|
1998-10-25 02:29:01 +02:00
|
|
|
|
<Title>Rules on INSERT, UPDATE and DELETE</Title>
|
|
|
|
|
|
|
|
|
|
<Sect2>
|
2000-12-12 06:07:59 +01:00
|
|
|
|
<Title>Differences from View Rules</Title>
|
1998-04-28 16:52:46 +02:00
|
|
|
|
|
|
|
|
|
<Para>
|
1998-10-25 02:29:01 +02:00
|
|
|
|
Rules that are defined ON INSERT, UPDATE and DELETE are
|
|
|
|
|
totally different from the view rules described
|
|
|
|
|
in the previous section. First, their CREATE RULE
|
|
|
|
|
command allows more:
|
|
|
|
|
|
|
|
|
|
<ItemizedList>
|
|
|
|
|
<ListItem>
|
|
|
|
|
<Para>
|
|
|
|
|
They can have no action.
|
|
|
|
|
</Para>
|
|
|
|
|
</ListItem>
|
|
|
|
|
|
|
|
|
|
<ListItem>
|
|
|
|
|
<Para>
|
|
|
|
|
They can have multiple actions.
|
|
|
|
|
</Para>
|
|
|
|
|
</ListItem>
|
|
|
|
|
|
|
|
|
|
<ListItem>
|
|
|
|
|
<Para>
|
|
|
|
|
The keyword INSTEAD is optional.
|
|
|
|
|
</Para>
|
|
|
|
|
</ListItem>
|
|
|
|
|
|
|
|
|
|
<ListItem>
|
|
|
|
|
<Para>
|
|
|
|
|
The pseudo relations NEW and OLD become useful.
|
|
|
|
|
</Para>
|
|
|
|
|
</ListItem>
|
|
|
|
|
|
|
|
|
|
<ListItem>
|
|
|
|
|
<Para>
|
|
|
|
|
They can have rule qualifications.
|
|
|
|
|
</Para>
|
|
|
|
|
</ListItem>
|
|
|
|
|
</ItemizedList>
|
|
|
|
|
|
2001-09-10 23:58:47 +02:00
|
|
|
|
Second, they don't modify the parse tree in place. Instead they
|
|
|
|
|
create zero or many new parse trees and can throw away the
|
1998-10-25 02:29:01 +02:00
|
|
|
|
original one.
|
|
|
|
|
</Para>
|
1998-12-29 03:24:47 +01:00
|
|
|
|
</sect2>
|
1998-10-25 02:29:01 +02:00
|
|
|
|
<Sect2>
|
|
|
|
|
<Title>How These Rules Work</Title>
|
1998-04-28 16:52:46 +02:00
|
|
|
|
|
|
|
|
|
<Para>
|
1998-10-25 02:29:01 +02:00
|
|
|
|
Keep the syntax
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
CREATE RULE rule_name AS ON event
|
|
|
|
|
TO object [WHERE rule_qualification]
|
|
|
|
|
DO [INSTEAD] [action | (actions) | NOTHING];
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
in mind.
|
|
|
|
|
In the following, "update rules" means rules that are defined
|
|
|
|
|
ON INSERT, UPDATE or DELETE.
|
1998-04-28 16:52:46 +02:00
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
1998-10-25 02:29:01 +02:00
|
|
|
|
Update rules get applied by the rule system when the result
|
2001-09-10 23:58:47 +02:00
|
|
|
|
relation and the command type of a parse tree are equal to the
|
1998-10-25 02:29:01 +02:00
|
|
|
|
object and event given in the CREATE RULE command.
|
2001-09-10 23:58:47 +02:00
|
|
|
|
For update rules, the rule system creates a list of parse trees.
|
|
|
|
|
Initially the parse tree list is empty.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
There can be zero (NOTHING keyword), one or multiple actions.
|
|
|
|
|
To simplify, we look at a rule with one action. This rule
|
|
|
|
|
can have a qualification or not and it can be INSTEAD or not.
|
|
|
|
|
</Para>
|
1998-04-28 16:52:46 +02:00
|
|
|
|
|
|
|
|
|
<Para>
|
1998-10-25 02:29:01 +02:00
|
|
|
|
What is a rule qualification? It is a restriction that tells
|
|
|
|
|
when the actions of the rule should be done and when not. This
|
|
|
|
|
qualification can only reference the NEW and/or OLD pseudo relations
|
|
|
|
|
which are basically the relation given as object (but with a
|
|
|
|
|
special meaning).
|
1998-04-28 16:52:46 +02:00
|
|
|
|
</Para>
|
|
|
|
|
|
1998-10-25 02:29:01 +02:00
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
So we have four cases that produce the following parse trees for
|
1998-10-25 02:29:01 +02:00
|
|
|
|
a one-action rule.
|
1998-04-28 16:52:46 +02:00
|
|
|
|
</Para>
|
1998-10-25 02:29:01 +02:00
|
|
|
|
<Para>
|
|
|
|
|
<ItemizedList>
|
|
|
|
|
<ListItem>
|
|
|
|
|
<Para>
|
|
|
|
|
No qualification and not INSTEAD:
|
|
|
|
|
<ItemizedList>
|
|
|
|
|
<ListItem>
|
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
The parse tree from the rule action where the
|
|
|
|
|
original parse tree's qualification has been added.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
</Para>
|
|
|
|
|
</ListItem>
|
|
|
|
|
</ItemizedList>
|
|
|
|
|
</Para>
|
|
|
|
|
</ListItem>
|
|
|
|
|
|
|
|
|
|
<ListItem>
|
|
|
|
|
<Para>
|
|
|
|
|
No qualification but INSTEAD:
|
|
|
|
|
<ItemizedList>
|
|
|
|
|
<ListItem>
|
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
The parse tree from the rule action where the
|
|
|
|
|
original parse tree's qualification has been added.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
</Para>
|
|
|
|
|
</ListItem>
|
|
|
|
|
</ItemizedList>
|
|
|
|
|
</Para>
|
|
|
|
|
</ListItem>
|
|
|
|
|
|
|
|
|
|
<ListItem>
|
|
|
|
|
<Para>
|
|
|
|
|
Qualification given and not INSTEAD:
|
|
|
|
|
<ItemizedList>
|
|
|
|
|
<ListItem>
|
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
The parse tree from the rule action where the rule
|
|
|
|
|
qualification and the original parse tree's
|
1998-10-25 02:29:01 +02:00
|
|
|
|
qualification have been added.
|
|
|
|
|
</Para>
|
|
|
|
|
</ListItem>
|
|
|
|
|
</ItemizedList>
|
|
|
|
|
</Para>
|
|
|
|
|
</ListItem>
|
|
|
|
|
|
|
|
|
|
<ListItem>
|
|
|
|
|
<Para>
|
|
|
|
|
Qualification given and INSTEAD:
|
|
|
|
|
<ItemizedList>
|
|
|
|
|
<ListItem>
|
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
The parse tree from the rule action where the rule
|
|
|
|
|
qualification and the original parse tree's
|
1998-10-25 02:29:01 +02:00
|
|
|
|
qualification have been added.
|
|
|
|
|
</Para>
|
|
|
|
|
</ListItem>
|
|
|
|
|
|
|
|
|
|
<ListItem>
|
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
The original parse tree where the negated rule
|
1998-10-25 02:29:01 +02:00
|
|
|
|
qualification has been added.
|
|
|
|
|
</Para>
|
|
|
|
|
</ListItem>
|
|
|
|
|
</ItemizedList>
|
|
|
|
|
</Para>
|
|
|
|
|
</ListItem>
|
|
|
|
|
</ItemizedList>
|
1998-12-29 03:24:47 +01:00
|
|
|
|
</para>
|
1998-04-28 16:52:46 +02:00
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
Finally, if the rule is not INSTEAD, the unchanged original parse tree is
|
1998-10-25 02:29:01 +02:00
|
|
|
|
added to the list. Since only qualified INSTEAD rules already add the
|
2001-09-10 23:58:47 +02:00
|
|
|
|
original parse tree, we end up with either one or two output parse trees
|
1998-10-25 02:29:01 +02:00
|
|
|
|
for a rule with one action.
|
|
|
|
|
</Para>
|
1998-04-28 16:52:46 +02:00
|
|
|
|
|
2001-07-10 01:50:32 +02:00
|
|
|
|
<Para>
|
|
|
|
|
For ON INSERT rules, the original query (if not suppressed by INSTEAD)
|
|
|
|
|
is done before any actions added by rules. This allows the actions to
|
|
|
|
|
see the inserted row(s). But for ON UPDATE and ON
|
|
|
|
|
DELETE rules, the original query is done after the actions added by rules.
|
|
|
|
|
This ensures that the actions can see the to-be-updated or to-be-deleted
|
|
|
|
|
rows; otherwise, the actions might do nothing because they find no rows
|
|
|
|
|
matching their qualifications.
|
|
|
|
|
</Para>
|
|
|
|
|
|
1998-04-28 16:52:46 +02:00
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
The parse trees generated from rule actions are thrown into the
|
1998-10-25 02:29:01 +02:00
|
|
|
|
rewrite system again and maybe more rules get applied resulting
|
2001-09-10 23:58:47 +02:00
|
|
|
|
in more or less parse trees.
|
|
|
|
|
So the parse trees in the rule actions must have either another command type
|
|
|
|
|
or another result relation. Otherwise this recursive process will end up in a loop.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
There is a compiled in recursion limit of currently 10 iterations.
|
|
|
|
|
If after 10 iterations there are still update rules to apply the
|
2001-07-10 01:50:32 +02:00
|
|
|
|
rule system assumes a loop over multiple rule definitions and reports
|
|
|
|
|
an error.
|
1998-04-28 16:52:46 +02:00
|
|
|
|
</Para>
|
|
|
|
|
|
1998-10-25 02:29:01 +02:00
|
|
|
|
<Para>
|
|
|
|
|
The parsetrees found in the actions of the <Filename>pg_rewrite</Filename>
|
|
|
|
|
system catalog are only templates. Since they can reference the
|
|
|
|
|
rangetable entries for NEW and OLD, some substitutions have to be made
|
|
|
|
|
before they can be used. For any reference to NEW, the targetlist of
|
|
|
|
|
the original query is searched for a corresponding entry. If found,
|
2000-12-12 06:07:59 +01:00
|
|
|
|
that entry's expression replaces the reference. Otherwise
|
|
|
|
|
NEW means the same as OLD (for an UPDATE) or is replaced by NULL
|
|
|
|
|
(for an INSERT). Any reference to OLD is replaced by a
|
1998-10-25 02:29:01 +02:00
|
|
|
|
reference to the rangetable entry which is the resultrelation.
|
1998-04-28 16:52:46 +02:00
|
|
|
|
</Para>
|
|
|
|
|
|
2000-12-12 06:07:59 +01:00
|
|
|
|
<Para>
|
|
|
|
|
After we are done applying update rules, we apply view rules to the
|
|
|
|
|
produced parsetree(s). Views cannot insert new update actions so
|
|
|
|
|
there is no need to apply update rules to the output of view rewriting.
|
|
|
|
|
</Para>
|
|
|
|
|
|
1998-10-25 02:29:01 +02:00
|
|
|
|
<Sect3>
|
|
|
|
|
<Title>A First Rule Step by Step</Title>
|
|
|
|
|
|
1998-04-28 16:52:46 +02:00
|
|
|
|
<Para>
|
1998-10-25 02:29:01 +02:00
|
|
|
|
We want to trace changes to the sl_avail column in the
|
|
|
|
|
<Filename>shoelace_data</Filename> relation. So we setup a
|
2000-12-12 06:07:59 +01:00
|
|
|
|
log table and a rule that conditionally writes a log entry when
|
|
|
|
|
an UPDATE is performed on <Filename>shoelace_data</Filename>.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
|
1998-04-28 16:52:46 +02:00
|
|
|
|
<ProgramListing>
|
1998-10-25 02:29:01 +02:00
|
|
|
|
CREATE TABLE shoelace_log (
|
|
|
|
|
sl_name char(10), -- shoelace changed
|
|
|
|
|
sl_avail integer, -- new available value
|
2001-01-20 21:59:29 +01:00
|
|
|
|
log_who text, -- who did it
|
|
|
|
|
log_when timestamp -- when
|
1998-10-25 02:29:01 +02:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
CREATE RULE log_shoelace AS ON UPDATE TO shoelace_data
|
|
|
|
|
WHERE NEW.sl_avail != OLD.sl_avail
|
|
|
|
|
DO INSERT INTO shoelace_log VALUES (
|
|
|
|
|
NEW.sl_name,
|
|
|
|
|
NEW.sl_avail,
|
2001-01-20 21:59:29 +01:00
|
|
|
|
current_user,
|
|
|
|
|
current_timestamp
|
1998-10-25 02:29:01 +02:00
|
|
|
|
);
|
1998-04-28 16:52:46 +02:00
|
|
|
|
</ProgramListing>
|
1998-10-25 02:29:01 +02:00
|
|
|
|
</Para>
|
|
|
|
|
|
1998-04-28 16:52:46 +02:00
|
|
|
|
<Para>
|
1998-10-25 02:29:01 +02:00
|
|
|
|
Now Al does
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
al_bundy=> UPDATE shoelace_data SET sl_avail = 6
|
|
|
|
|
al_bundy-> WHERE sl_name = 'sl7';
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
and we look at the logtable.
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
al_bundy=> SELECT * FROM shoelace_log;
|
|
|
|
|
sl_name |sl_avail|log_who|log_when
|
|
|
|
|
----------+--------+-------+--------------------------------
|
|
|
|
|
sl7 | 6|Al |Tue Oct 20 16:14:45 1998 MET DST
|
|
|
|
|
(1 row)
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
That's what we expected. What happened in the background is the following.
|
|
|
|
|
The parser created the parsetree (this time the parts of the original
|
|
|
|
|
parsetree are highlighted because the base of operations is the
|
|
|
|
|
rule action for update rules).
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
<FirstTerm>UPDATE shoelace_data SET sl_avail = 6
|
|
|
|
|
FROM shoelace_data shoelace_data
|
|
|
|
|
WHERE bpchareq(shoelace_data.sl_name, 'sl7');</FirstTerm>
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
There is a rule 'log_shoelace' that is ON UPDATE with the rule
|
|
|
|
|
qualification expression
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
int4ne(NEW.sl_avail, OLD.sl_avail)
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
and one action
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
2000-12-12 06:07:59 +01:00
|
|
|
|
INSERT INTO shoelace_log VALUES(
|
1998-10-25 02:29:01 +02:00
|
|
|
|
*NEW*.sl_name, *NEW*.sl_avail,
|
2001-01-20 21:59:29 +01:00
|
|
|
|
current_user, current_timestamp
|
2000-12-12 06:07:59 +01:00
|
|
|
|
FROM shoelace_data *NEW*, shoelace_data *OLD*;
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
This is a little strange-looking since you can't normally write
|
|
|
|
|
INSERT ... VALUES ... FROM. The FROM clause here is just to indicate
|
|
|
|
|
that there are rangetable entries in the parsetree for *NEW* and *OLD*.
|
|
|
|
|
These are needed so that they can be referenced by variables in the
|
|
|
|
|
INSERT command's querytree.
|
1998-04-28 16:52:46 +02:00
|
|
|
|
</Para>
|
|
|
|
|
|
1998-10-25 02:29:01 +02:00
|
|
|
|
<Para>
|
|
|
|
|
The rule is a qualified non-INSTEAD rule, so the rule system
|
2001-09-10 23:58:47 +02:00
|
|
|
|
has to return two parse trees: the modified rule action and the original
|
|
|
|
|
parsetree. In the first step the range table of the original query is
|
|
|
|
|
incorporated into the rule's action parse tree. This results in
|
1998-10-25 02:29:01 +02:00
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
2000-12-12 06:07:59 +01:00
|
|
|
|
INSERT INTO shoelace_log VALUES(
|
|
|
|
|
*NEW*.sl_name, *NEW*.sl_avail,
|
2001-01-20 21:59:29 +01:00
|
|
|
|
current_user, current_timestamp
|
2000-12-12 06:07:59 +01:00
|
|
|
|
FROM shoelace_data *NEW*, shoelace_data *OLD*,
|
|
|
|
|
<FirstTerm>shoelace_data shoelace_data</FirstTerm>;
|
1998-10-25 02:29:01 +02:00
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
In step 2 the rule qualification is added to it, so the result set
|
|
|
|
|
is restricted to rows where sl_avail changes.
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
2000-12-12 06:07:59 +01:00
|
|
|
|
INSERT INTO shoelace_log VALUES(
|
|
|
|
|
*NEW*.sl_name, *NEW*.sl_avail,
|
2001-01-20 21:59:29 +01:00
|
|
|
|
current_user, current_timestamp
|
2000-12-12 06:07:59 +01:00
|
|
|
|
FROM shoelace_data *NEW*, shoelace_data *OLD*,
|
|
|
|
|
shoelace_data shoelace_data
|
1998-10-25 02:29:01 +02:00
|
|
|
|
<FirstTerm>WHERE int4ne(*NEW*.sl_avail, *OLD*.sl_avail)</FirstTerm>;
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
2000-12-12 06:07:59 +01:00
|
|
|
|
This is even stranger-looking, since INSERT ... VALUES doesn't have
|
|
|
|
|
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.
|
|
|
|
|
|
2001-09-10 23:58:47 +02:00
|
|
|
|
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.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
2000-12-12 06:07:59 +01:00
|
|
|
|
INSERT INTO shoelace_log VALUES(
|
|
|
|
|
*NEW*.sl_name, *NEW*.sl_avail,
|
2001-01-20 21:59:29 +01:00
|
|
|
|
current_user, current_timestamp
|
2000-12-12 06:07:59 +01:00
|
|
|
|
FROM shoelace_data *NEW*, shoelace_data *OLD*,
|
|
|
|
|
shoelace_data shoelace_data
|
1998-10-25 02:29:01 +02:00
|
|
|
|
WHERE int4ne(*NEW*.sl_avail, *OLD*.sl_avail)
|
|
|
|
|
<FirstTerm>AND bpchareq(shoelace_data.sl_name, 'sl7')</FirstTerm>;
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
2001-09-10 23:58:47 +02:00
|
|
|
|
Step 4 substitutes NEW references by the target list entries from the
|
|
|
|
|
original parse tree or with the matching variable references
|
1998-10-25 02:29:01 +02:00
|
|
|
|
from the result relation.
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
2000-12-12 06:07:59 +01:00
|
|
|
|
INSERT INTO shoelace_log VALUES(
|
1998-10-25 02:29:01 +02:00
|
|
|
|
<FirstTerm>shoelace_data.sl_name</FirstTerm>, <FirstTerm>6</FirstTerm>,
|
2001-01-20 21:59:29 +01:00
|
|
|
|
current_user, current_timestamp
|
2000-12-12 06:07:59 +01:00
|
|
|
|
FROM shoelace_data *NEW*, shoelace_data *OLD*,
|
|
|
|
|
shoelace_data shoelace_data
|
1998-10-25 02:29:01 +02:00
|
|
|
|
WHERE int4ne(<FirstTerm>6</FirstTerm>, *OLD*.sl_avail)
|
|
|
|
|
AND bpchareq(shoelace_data.sl_name, 'sl7');
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
2001-09-10 23:58:47 +02:00
|
|
|
|
Step 5 changes OLD references into result relation references.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
2000-12-12 06:07:59 +01:00
|
|
|
|
INSERT INTO shoelace_log VALUES(
|
1998-10-25 02:29:01 +02:00
|
|
|
|
shoelace_data.sl_name, 6,
|
2001-01-20 21:59:29 +01:00
|
|
|
|
current_user, current_timestamp
|
2000-12-12 06:07:59 +01:00
|
|
|
|
FROM shoelace_data *NEW*, shoelace_data *OLD*,
|
|
|
|
|
shoelace_data shoelace_data
|
1998-10-25 02:29:01 +02:00
|
|
|
|
WHERE int4ne(6, <FirstTerm>shoelace_data.sl_avail</FirstTerm>)
|
|
|
|
|
AND bpchareq(shoelace_data.sl_name, 'sl7');
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
2000-12-12 06:07:59 +01:00
|
|
|
|
That's it. Since the rule is not INSTEAD, we also output the
|
2001-09-10 23:58:47 +02:00
|
|
|
|
original parse tree. In short, the output from the rule system
|
|
|
|
|
is a list of two parse trees that are the same as the statements:
|
1998-10-25 02:29:01 +02:00
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
2000-12-12 06:07:59 +01:00
|
|
|
|
INSERT INTO shoelace_log VALUES(
|
1998-10-25 02:29:01 +02:00
|
|
|
|
shoelace_data.sl_name, 6,
|
2001-01-20 21:59:29 +01:00
|
|
|
|
current_user, current_timestamp
|
1998-10-25 02:29:01 +02:00
|
|
|
|
FROM shoelace_data
|
|
|
|
|
WHERE 6 != shoelace_data.sl_avail
|
|
|
|
|
AND shoelace_data.sl_name = 'sl7';
|
|
|
|
|
|
|
|
|
|
UPDATE shoelace_data SET sl_avail = 6
|
|
|
|
|
WHERE sl_name = 'sl7';
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
These are executed in this order and that is exactly what
|
2000-12-12 06:07:59 +01:00
|
|
|
|
the rule defines. The substitutions and the qualifications
|
|
|
|
|
added ensure that if the original query would be, say,
|
1998-10-25 02:29:01 +02:00
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
UPDATE shoelace_data SET sl_color = 'green'
|
|
|
|
|
WHERE sl_name = 'sl7';
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
2000-12-12 06:07:59 +01:00
|
|
|
|
no log entry would get written. This
|
2001-09-10 23:58:47 +02:00
|
|
|
|
time the original parse tree does not contain a target list
|
2000-12-12 06:07:59 +01:00
|
|
|
|
entry for sl_avail, so NEW.sl_avail will get replaced by
|
1998-10-25 02:29:01 +02:00
|
|
|
|
shoelace_data.sl_avail resulting in the extra query
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
2000-12-12 06:07:59 +01:00
|
|
|
|
INSERT INTO shoelace_log VALUES(
|
1998-10-25 02:29:01 +02:00
|
|
|
|
shoelace_data.sl_name, <FirstTerm>shoelace_data.sl_avail</FirstTerm>,
|
2001-01-20 21:59:29 +01:00
|
|
|
|
current_user, current_timestamp)
|
1998-10-25 02:29:01 +02:00
|
|
|
|
FROM shoelace_data
|
|
|
|
|
WHERE <FirstTerm>shoelace_data.sl_avail</FirstTerm> != shoelace_data.sl_avail
|
|
|
|
|
AND shoelace_data.sl_name = 'sl7';
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
2000-12-12 06:07:59 +01:00
|
|
|
|
and that qualification will never be true. It will also
|
1998-10-25 02:29:01 +02:00
|
|
|
|
work if the original query modifies multiple rows. So if Al
|
|
|
|
|
would issue the command
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
UPDATE shoelace_data SET sl_avail = 0
|
|
|
|
|
WHERE sl_color = 'black';
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
four rows in fact get updated (sl1, sl2, sl3 and sl4).
|
|
|
|
|
But sl3 already has sl_avail = 0. This time, the original
|
2001-09-10 23:58:47 +02:00
|
|
|
|
parse trees qualification is different and that results
|
|
|
|
|
in the extra parse tree
|
1998-10-25 02:29:01 +02:00
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
INSERT INTO shoelace_log SELECT
|
|
|
|
|
shoelace_data.sl_name, 0,
|
2001-01-20 21:59:29 +01:00
|
|
|
|
current_user, current_timestamp
|
1998-10-25 02:29:01 +02:00
|
|
|
|
FROM shoelace_data
|
|
|
|
|
WHERE 0 != shoelace_data.sl_avail
|
|
|
|
|
AND <FirstTerm>shoelace_data.sl_color = 'black'</FirstTerm>;
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
2001-09-10 23:58:47 +02:00
|
|
|
|
This parse tree will surely insert three new log entries. And
|
1998-10-25 02:29:01 +02:00
|
|
|
|
that's absolutely correct.
|
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
Here we can see why it is important that the original parse tree is
|
2001-07-10 01:50:32 +02:00
|
|
|
|
executed last.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
If the UPDATE would have been executed first, all the rows
|
|
|
|
|
are already set to zero, so the logging INSERT
|
|
|
|
|
would not find any row where 0 != shoelace_data.sl_avail.
|
|
|
|
|
</Para>
|
|
|
|
|
</Sect3>
|
|
|
|
|
|
|
|
|
|
</Sect2>
|
|
|
|
|
|
|
|
|
|
<Sect2>
|
|
|
|
|
<Title>Cooperation with Views</Title>
|
|
|
|
|
|
|
|
|
|
<Para>
|
|
|
|
|
A simple way to protect view relations from the mentioned
|
2000-12-12 06:07:59 +01:00
|
|
|
|
possibility that someone can try to INSERT, UPDATE and DELETE
|
2001-09-10 23:58:47 +02:00
|
|
|
|
on them is to let those parse trees get
|
1998-10-25 02:29:01 +02:00
|
|
|
|
thrown away. We create the rules
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
CREATE RULE shoe_ins_protect AS ON INSERT TO shoe
|
|
|
|
|
DO INSTEAD NOTHING;
|
|
|
|
|
CREATE RULE shoe_upd_protect AS ON UPDATE TO shoe
|
|
|
|
|
DO INSTEAD NOTHING;
|
|
|
|
|
CREATE RULE shoe_del_protect AS ON DELETE TO shoe
|
|
|
|
|
DO INSTEAD NOTHING;
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
If Al now tries to do any of these operations on the view
|
|
|
|
|
relation <Filename>shoe</Filename>, the rule system will
|
|
|
|
|
apply the rules. Since the rules have
|
|
|
|
|
no actions and are INSTEAD, the resulting list of
|
2001-09-10 23:58:47 +02:00
|
|
|
|
parse trees will be empty and the whole query will become
|
1998-10-25 02:29:01 +02:00
|
|
|
|
nothing because there is nothing left to be optimized or
|
|
|
|
|
executed after the rule system is done with it.
|
|
|
|
|
|
|
|
|
|
<Note>
|
|
|
|
|
<Title>Note</Title>
|
|
|
|
|
<Para>
|
2000-12-12 06:07:59 +01:00
|
|
|
|
This way might irritate frontend applications because
|
1998-10-25 02:29:01 +02:00
|
|
|
|
absolutely nothing happened on the database and thus, the
|
|
|
|
|
backend will not return anything for the query. Not
|
2001-09-10 23:58:47 +02:00
|
|
|
|
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.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
</Para>
|
|
|
|
|
</Note>
|
|
|
|
|
|
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
|
|
|
|
A more sophisticated way to use the rule system is to
|
2001-09-10 23:58:47 +02:00
|
|
|
|
create rules that rewrite the parse tree into one that
|
1998-10-25 02:29:01 +02:00
|
|
|
|
does the right operation on the real tables. To do that
|
|
|
|
|
on the <Filename>shoelace</Filename> view, we create
|
|
|
|
|
the following rules:
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
CREATE RULE shoelace_ins AS ON INSERT TO shoelace
|
|
|
|
|
DO INSTEAD
|
|
|
|
|
INSERT INTO shoelace_data VALUES (
|
|
|
|
|
NEW.sl_name,
|
|
|
|
|
NEW.sl_avail,
|
|
|
|
|
NEW.sl_color,
|
|
|
|
|
NEW.sl_len,
|
|
|
|
|
NEW.sl_unit);
|
|
|
|
|
|
|
|
|
|
CREATE RULE shoelace_upd AS ON UPDATE TO shoelace
|
|
|
|
|
DO INSTEAD
|
|
|
|
|
UPDATE shoelace_data SET
|
|
|
|
|
sl_name = NEW.sl_name,
|
|
|
|
|
sl_avail = NEW.sl_avail,
|
|
|
|
|
sl_color = NEW.sl_color,
|
|
|
|
|
sl_len = NEW.sl_len,
|
|
|
|
|
sl_unit = NEW.sl_unit
|
|
|
|
|
WHERE sl_name = OLD.sl_name;
|
|
|
|
|
|
|
|
|
|
CREATE RULE shoelace_del AS ON DELETE TO shoelace
|
|
|
|
|
DO INSTEAD
|
|
|
|
|
DELETE FROM shoelace_data
|
|
|
|
|
WHERE sl_name = OLD.sl_name;
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
Now there is a pack of shoelaces arriving in Al's shop and it has
|
2001-09-10 23:58:47 +02:00
|
|
|
|
a big part list. Al is not that good in calculating and so
|
1998-10-25 02:29:01 +02:00
|
|
|
|
we don't want him to manually update the shoelace view.
|
|
|
|
|
Instead we setup two little tables, one where he can
|
2001-09-10 23:58:47 +02:00
|
|
|
|
insert the items from the part list and one with a special
|
2000-12-12 06:07:59 +01:00
|
|
|
|
trick. The create commands for these are:
|
1998-10-25 02:29:01 +02:00
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
CREATE TABLE shoelace_arrive (
|
|
|
|
|
arr_name char(10),
|
|
|
|
|
arr_quant integer
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
CREATE TABLE shoelace_ok (
|
|
|
|
|
ok_name char(10),
|
|
|
|
|
ok_quant integer
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
CREATE RULE shoelace_ok_ins AS ON INSERT TO shoelace_ok
|
|
|
|
|
DO INSTEAD
|
|
|
|
|
UPDATE shoelace SET
|
|
|
|
|
sl_avail = sl_avail + NEW.ok_quant
|
|
|
|
|
WHERE sl_name = NEW.ok_name;
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
Now Al can sit down and do whatever until
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
al_bundy=> SELECT * FROM shoelace_arrive;
|
|
|
|
|
arr_name |arr_quant
|
|
|
|
|
----------+---------
|
|
|
|
|
sl3 | 10
|
|
|
|
|
sl6 | 20
|
|
|
|
|
sl8 | 20
|
|
|
|
|
(3 rows)
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
2000-12-12 06:07:59 +01:00
|
|
|
|
is exactly what's on the part list. We take a quick look
|
1998-10-25 02:29:01 +02:00
|
|
|
|
at the current data,
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
2001-09-12 03:22:25 +02:00
|
|
|
|
al_bundy=> SELECT * FROM shoelace;
|
1998-10-25 02:29:01 +02:00
|
|
|
|
sl_name |sl_avail|sl_color |sl_len|sl_unit |sl_len_cm
|
|
|
|
|
----------+--------+----------+------+--------+---------
|
|
|
|
|
sl1 | 5|black | 80|cm | 80
|
|
|
|
|
sl2 | 6|black | 100|cm | 100
|
|
|
|
|
sl7 | 6|brown | 60|cm | 60
|
|
|
|
|
sl3 | 0|black | 35|inch | 88.9
|
|
|
|
|
sl4 | 8|black | 40|inch | 101.6
|
|
|
|
|
sl8 | 1|brown | 40|inch | 101.6
|
|
|
|
|
sl5 | 4|brown | 1|m | 100
|
|
|
|
|
sl6 | 0|brown | 0.9|m | 90
|
|
|
|
|
(8 rows)
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
move the arrived shoelaces in
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
al_bundy=> INSERT INTO shoelace_ok SELECT * FROM shoelace_arrive;
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
and check the results
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
al_bundy=> SELECT * FROM shoelace ORDER BY sl_name;
|
|
|
|
|
sl_name |sl_avail|sl_color |sl_len|sl_unit |sl_len_cm
|
|
|
|
|
----------+--------+----------+------+--------+---------
|
|
|
|
|
sl1 | 5|black | 80|cm | 80
|
|
|
|
|
sl2 | 6|black | 100|cm | 100
|
|
|
|
|
sl7 | 6|brown | 60|cm | 60
|
|
|
|
|
sl4 | 8|black | 40|inch | 101.6
|
|
|
|
|
sl3 | 10|black | 35|inch | 88.9
|
|
|
|
|
sl8 | 21|brown | 40|inch | 101.6
|
|
|
|
|
sl5 | 4|brown | 1|m | 100
|
|
|
|
|
sl6 | 20|brown | 0.9|m | 90
|
|
|
|
|
(8 rows)
|
|
|
|
|
|
|
|
|
|
al_bundy=> SELECT * FROM shoelace_log;
|
|
|
|
|
sl_name |sl_avail|log_who|log_when
|
|
|
|
|
----------+--------+-------+--------------------------------
|
|
|
|
|
sl7 | 6|Al |Tue Oct 20 19:14:45 1998 MET DST
|
|
|
|
|
sl3 | 10|Al |Tue Oct 20 19:25:16 1998 MET DST
|
|
|
|
|
sl6 | 20|Al |Tue Oct 20 19:25:16 1998 MET DST
|
|
|
|
|
sl8 | 21|Al |Tue Oct 20 19:25:16 1998 MET DST
|
|
|
|
|
(4 rows)
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
It's a long way from the one INSERT ... SELECT to these
|
2000-12-12 06:07:59 +01:00
|
|
|
|
results. And its description will be the last in this
|
2001-09-10 23:58:47 +02:00
|
|
|
|
document (but not the last example :-). First there was the parser's output
|
1998-10-25 02:29:01 +02:00
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
INSERT INTO shoelace_ok SELECT
|
|
|
|
|
shoelace_arrive.arr_name, shoelace_arrive.arr_quant
|
|
|
|
|
FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok;
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
Now the first rule 'shoelace_ok_ins' is applied and turns it
|
|
|
|
|
into
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
UPDATE shoelace SET
|
|
|
|
|
sl_avail = int4pl(shoelace.sl_avail, shoelace_arrive.arr_quant)
|
|
|
|
|
FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok,
|
|
|
|
|
shoelace_ok *OLD*, shoelace_ok *NEW*,
|
|
|
|
|
shoelace shoelace
|
|
|
|
|
WHERE bpchareq(shoelace.sl_name, showlace_arrive.arr_name);
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
and throws away the original INSERT on <Filename>shoelace_ok</Filename>.
|
|
|
|
|
This rewritten query is passed to the rule system again and
|
2001-09-10 23:58:47 +02:00
|
|
|
|
the second applied rule <literal>shoelace_upd</literal> produced
|
1998-10-25 02:29:01 +02:00
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
UPDATE shoelace_data SET
|
|
|
|
|
sl_name = shoelace.sl_name,
|
|
|
|
|
sl_avail = int4pl(shoelace.sl_avail, shoelace_arrive.arr_quant),
|
|
|
|
|
sl_color = shoelace.sl_color,
|
|
|
|
|
sl_len = shoelace.sl_len,
|
|
|
|
|
sl_unit = shoelace.sl_unit
|
|
|
|
|
FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok,
|
|
|
|
|
shoelace_ok *OLD*, shoelace_ok *NEW*,
|
|
|
|
|
shoelace shoelace, shoelace *OLD*,
|
|
|
|
|
shoelace *NEW*, shoelace_data showlace_data
|
|
|
|
|
WHERE bpchareq(shoelace.sl_name, showlace_arrive.arr_name)
|
|
|
|
|
AND bpchareq(shoelace_data.sl_name, shoelace.sl_name);
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
2001-09-10 23:58:47 +02:00
|
|
|
|
Again it's an INSTEAD rule and the previous parse tree is trashed.
|
2000-12-12 06:07:59 +01:00
|
|
|
|
Note that this query still uses the view <Filename>shoelace</Filename>.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
But the rule system isn't finished with this loop so it continues
|
2001-09-10 23:58:47 +02:00
|
|
|
|
and applies the rule <literal>_RETshoelace</literal> on it and we get
|
1998-10-25 02:29:01 +02:00
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
UPDATE shoelace_data SET
|
|
|
|
|
sl_name = s.sl_name,
|
|
|
|
|
sl_avail = int4pl(s.sl_avail, shoelace_arrive.arr_quant),
|
|
|
|
|
sl_color = s.sl_color,
|
|
|
|
|
sl_len = s.sl_len,
|
|
|
|
|
sl_unit = s.sl_unit
|
|
|
|
|
FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok,
|
|
|
|
|
shoelace_ok *OLD*, shoelace_ok *NEW*,
|
|
|
|
|
shoelace shoelace, shoelace *OLD*,
|
|
|
|
|
shoelace *NEW*, shoelace_data showlace_data,
|
|
|
|
|
shoelace *OLD*, shoelace *NEW*,
|
|
|
|
|
shoelace_data s, unit u
|
|
|
|
|
WHERE bpchareq(s.sl_name, showlace_arrive.arr_name)
|
|
|
|
|
AND bpchareq(shoelace_data.sl_name, s.sl_name);
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
Again an update rule has been applied and so the wheel
|
|
|
|
|
turns on and we are in rewrite round 3. This time rule
|
2001-09-10 23:58:47 +02:00
|
|
|
|
<literal>log_shoelace</literal> gets applied what produces the extra
|
|
|
|
|
parse tree
|
1998-10-25 02:29:01 +02:00
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
INSERT INTO shoelace_log SELECT
|
|
|
|
|
s.sl_name,
|
|
|
|
|
int4pl(s.sl_avail, shoelace_arrive.arr_quant),
|
2001-01-20 21:59:29 +01:00
|
|
|
|
current_user,
|
|
|
|
|
current_timestamp
|
1998-10-25 02:29:01 +02:00
|
|
|
|
FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok,
|
|
|
|
|
shoelace_ok *OLD*, shoelace_ok *NEW*,
|
|
|
|
|
shoelace shoelace, shoelace *OLD*,
|
|
|
|
|
shoelace *NEW*, shoelace_data showlace_data,
|
|
|
|
|
shoelace *OLD*, shoelace *NEW*,
|
|
|
|
|
shoelace_data s, unit u,
|
|
|
|
|
shoelace_data *OLD*, shoelace_data *NEW*
|
|
|
|
|
shoelace_log shoelace_log
|
|
|
|
|
WHERE bpchareq(s.sl_name, showlace_arrive.arr_name)
|
|
|
|
|
AND bpchareq(shoelace_data.sl_name, s.sl_name);
|
|
|
|
|
AND int4ne(int4pl(s.sl_avail, shoelace_arrive.arr_quant),
|
|
|
|
|
s.sl_avail);
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
After that the rule system runs out of rules and returns the
|
2001-09-10 23:58:47 +02:00
|
|
|
|
generated parse trees.
|
|
|
|
|
So we end up with two final parse trees that are equal to the
|
1998-10-25 02:29:01 +02:00
|
|
|
|
<Acronym>SQL</Acronym> statements
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
INSERT INTO shoelace_log SELECT
|
|
|
|
|
s.sl_name,
|
|
|
|
|
s.sl_avail + shoelace_arrive.arr_quant,
|
2001-01-20 21:59:29 +01:00
|
|
|
|
current_user,
|
|
|
|
|
current_timestamp
|
1998-10-25 02:29:01 +02:00
|
|
|
|
FROM shoelace_arrive shoelace_arrive, shoelace_data shoelace_data,
|
|
|
|
|
shoelace_data s
|
|
|
|
|
WHERE s.sl_name = shoelace_arrive.arr_name
|
|
|
|
|
AND shoelace_data.sl_name = s.sl_name
|
|
|
|
|
AND s.sl_avail + shoelace_arrive.arr_quant != s.sl_avail;
|
|
|
|
|
|
|
|
|
|
UPDATE shoelace_data SET
|
|
|
|
|
sl_avail = shoelace_data.sl_avail + shoelace_arrive.arr_quant
|
1999-06-03 18:13:54 +02:00
|
|
|
|
FROM shoelace_arrive shoelace_arrive,
|
|
|
|
|
shoelace_data shoelace_data,
|
1998-10-25 02:29:01 +02:00
|
|
|
|
shoelace_data s
|
|
|
|
|
WHERE s.sl_name = shoelace_arrive.sl_name
|
|
|
|
|
AND shoelace_data.sl_name = s.sl_name;
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
The result is that data coming from one relation inserted into another,
|
|
|
|
|
changed into updates on a third, changed into updating
|
|
|
|
|
a fourth plus logging that final update in a fifth
|
|
|
|
|
gets reduced into two queries.
|
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
|
|
|
|
There is a little detail that's a bit ugly. Looking at
|
|
|
|
|
the two queries turns out, that the <Filename>shoelace_data</Filename>
|
2001-09-10 23:58:47 +02:00
|
|
|
|
relation appears twice in the range table where it could definitely
|
2000-12-12 06:07:59 +01:00
|
|
|
|
be reduced to one. The planner does not handle it and so the
|
1998-10-25 02:29:01 +02:00
|
|
|
|
execution plan for the rule systems output of the INSERT will be
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
Nested Loop
|
|
|
|
|
-> Merge Join
|
|
|
|
|
-> Seq Scan
|
|
|
|
|
-> Sort
|
|
|
|
|
-> Seq Scan on s
|
|
|
|
|
-> Seq Scan
|
|
|
|
|
-> Sort
|
|
|
|
|
-> Seq Scan on shoelace_arrive
|
|
|
|
|
-> Seq Scan on shoelace_data
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
2001-09-10 23:58:47 +02:00
|
|
|
|
while omitting the extra range table entry would result in a
|
1998-10-25 02:29:01 +02:00
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
Merge Join
|
|
|
|
|
-> Seq Scan
|
|
|
|
|
-> Sort
|
|
|
|
|
-> Seq Scan on s
|
|
|
|
|
-> Seq Scan
|
|
|
|
|
-> Sort
|
|
|
|
|
-> Seq Scan on shoelace_arrive
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
that totally produces the same entries in the log relation.
|
|
|
|
|
Thus, the rule system caused one extra scan on the
|
|
|
|
|
<Filename>shoelace_data</Filename> relation that is
|
|
|
|
|
absolutely not necessary. And the same obsolete scan
|
|
|
|
|
is done once more in the UPDATE. But it was a really hard
|
|
|
|
|
job to make that all possible at all.
|
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
|
|
|
|
A final demonstration of the <ProductName>Postgres</ProductName>
|
2000-12-12 06:07:59 +01:00
|
|
|
|
rule system and its power. There is a cute blonde that
|
1998-10-25 02:29:01 +02:00
|
|
|
|
sells shoelaces. And what Al could never realize, she's not
|
|
|
|
|
only cute, she's smart too - a little too smart. Thus, it
|
|
|
|
|
happens from time to time that Al orders shoelaces that
|
|
|
|
|
are absolutely not sellable. This time he ordered 1000 pairs
|
|
|
|
|
of magenta shoelaces and since another kind is currently not
|
|
|
|
|
available but he committed to buy some, he also prepared
|
|
|
|
|
his database for pink ones.
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
al_bundy=> INSERT INTO shoelace VALUES
|
|
|
|
|
al_bundy-> ('sl9', 0, 'pink', 35.0, 'inch', 0.0);
|
|
|
|
|
al_bundy=> INSERT INTO shoelace VALUES
|
|
|
|
|
al_bundy-> ('sl10', 1000, 'magenta', 40.0, 'inch', 0.0);
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
Since this happens often, we must lookup for shoelace entries,
|
|
|
|
|
that fit for absolutely no shoe sometimes. We could do that in
|
|
|
|
|
a complicated statement every time, or we can setup a view
|
|
|
|
|
for it. The view for this is
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
CREATE VIEW shoelace_obsolete AS
|
|
|
|
|
SELECT * FROM shoelace WHERE NOT EXISTS
|
|
|
|
|
(SELECT shoename FROM shoe WHERE slcolor = sl_color);
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
2000-12-12 06:07:59 +01:00
|
|
|
|
Its output is
|
1998-10-25 02:29:01 +02:00
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
al_bundy=> SELECT * FROM shoelace_obsolete;
|
|
|
|
|
sl_name |sl_avail|sl_color |sl_len|sl_unit |sl_len_cm
|
|
|
|
|
----------+--------+----------+------+--------+---------
|
|
|
|
|
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
|
|
|
|
|
throw 'em away, but that's another problem. The pink entry we delete.
|
|
|
|
|
To make it a little harder for <ProductName>Postgres</ProductName>,
|
|
|
|
|
we don't delete it directly. Instead we create one more view
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
CREATE VIEW shoelace_candelete AS
|
|
|
|
|
SELECT * FROM shoelace_obsolete WHERE sl_avail = 0;
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
and do it this way:
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
DELETE FROM shoelace WHERE EXISTS
|
|
|
|
|
(SELECT * FROM shoelace_candelete
|
|
|
|
|
WHERE sl_name = shoelace.sl_name);
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
2001-09-10 23:58:47 +02:00
|
|
|
|
Voil<69>:
|
1998-10-25 02:29:01 +02:00
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
al_bundy=> SELECT * FROM shoelace;
|
|
|
|
|
sl_name |sl_avail|sl_color |sl_len|sl_unit |sl_len_cm
|
|
|
|
|
----------+--------+----------+------+--------+---------
|
|
|
|
|
sl1 | 5|black | 80|cm | 80
|
|
|
|
|
sl2 | 6|black | 100|cm | 100
|
|
|
|
|
sl7 | 6|brown | 60|cm | 60
|
|
|
|
|
sl4 | 8|black | 40|inch | 101.6
|
|
|
|
|
sl3 | 10|black | 35|inch | 88.9
|
|
|
|
|
sl8 | 21|brown | 40|inch | 101.6
|
|
|
|
|
sl10 | 1000|magenta | 40|inch | 101.6
|
|
|
|
|
sl5 | 4|brown | 1|m | 100
|
|
|
|
|
sl6 | 20|brown | 0.9|m | 90
|
|
|
|
|
(9 rows)
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
A DELETE on a view, with a subselect qualification that
|
|
|
|
|
in total uses 4 nesting/joined views, where one of them
|
|
|
|
|
itself has a subselect qualification containing a view
|
|
|
|
|
and where calculated view columns are used,
|
|
|
|
|
gets rewritten into
|
2001-09-10 23:58:47 +02:00
|
|
|
|
one single parse tree that deletes the requested data
|
1998-10-25 02:29:01 +02:00
|
|
|
|
from a real table.
|
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
|
|
|
|
I think there are only a few situations out in the real
|
|
|
|
|
world, where such a construct is necessary. But
|
|
|
|
|
it makes me feel comfortable that it works.
|
|
|
|
|
|
|
|
|
|
<Note>
|
|
|
|
|
<Title>The truth is</Title>
|
|
|
|
|
<Para>
|
|
|
|
|
Doing this I found one more bug while writing this document.
|
|
|
|
|
But after fixing that I was a little amazed that it works at all.
|
|
|
|
|
</Para>
|
|
|
|
|
</Note>
|
|
|
|
|
</Para>
|
|
|
|
|
</Sect2>
|
|
|
|
|
|
|
|
|
|
</Sect1>
|
|
|
|
|
|
2000-09-29 22:21:34 +02:00
|
|
|
|
<Sect1 id="rules-permissions">
|
1998-10-25 02:29:01 +02:00
|
|
|
|
<Title>Rules and Permissions</Title>
|
|
|
|
|
|
|
|
|
|
<Para>
|
|
|
|
|
Due to rewriting of queries by the <ProductName>Postgres</ProductName>
|
|
|
|
|
rule system, other tables/views than those used in the original
|
|
|
|
|
query get accessed. Using update rules, this can include write access
|
|
|
|
|
to tables.
|
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
|
|
|
|
Rewrite rules don't have a separate owner. The owner of
|
|
|
|
|
a relation (table or view) is automatically the owner of the
|
|
|
|
|
rewrite rules that are defined for it.
|
|
|
|
|
The <ProductName>Postgres</ProductName> rule system changes the
|
2001-09-10 23:58:47 +02:00
|
|
|
|
behavior of the default access control system. Relations that
|
2000-12-12 06:07:59 +01:00
|
|
|
|
are used due to rules get checked against the
|
|
|
|
|
permissions of the rule owner, not the user invoking the rule.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
This means, that a user does only need the required permissions
|
|
|
|
|
for the tables/views he names in his queries.
|
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
|
|
|
|
For example: A user has a list of phone numbers where some of
|
|
|
|
|
them are private, the others are of interest for the secretary of the office.
|
|
|
|
|
He can construct the following:
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
CREATE TABLE phone_data (person text, phone text, private bool);
|
|
|
|
|
CREATE VIEW phone_number AS
|
|
|
|
|
SELECT person, phone FROM phone_data WHERE NOT private;
|
|
|
|
|
GRANT SELECT ON phone_number TO secretary;
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
Nobody except him (and the database superusers) can access the
|
|
|
|
|
phone_data table. But due to the GRANT, the secretary can SELECT from the
|
|
|
|
|
phone_number view. The rule system will rewrite
|
|
|
|
|
the SELECT from phone_number into a SELECT from phone_data and add the qualification
|
|
|
|
|
that only entries where private is false are wanted. Since the
|
|
|
|
|
user is the owner of phone_number, the read access to phone_data
|
|
|
|
|
is now checked against his permissions and the query is considered
|
2000-12-12 06:07:59 +01:00
|
|
|
|
granted. The check for accessing phone_number is also performed,
|
|
|
|
|
but this is done against the invoking user, so nobody but the user and the
|
|
|
|
|
secretary can use it.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
|
|
|
|
The permissions are checked rule by rule. So the secretary is for now the
|
|
|
|
|
only one who can see the public phone numbers. But the secretary can setup
|
|
|
|
|
another view and grant access to that to public. Then, anyone
|
|
|
|
|
can see the phone_number data through the secretaries view.
|
|
|
|
|
What the secretary cannot do is to create a view that directly
|
|
|
|
|
accesses phone_data (actually he can, but it will not work since
|
|
|
|
|
every access aborts the transaction during the permission checks).
|
|
|
|
|
And as soon as the user will notice, that the secretary opened
|
|
|
|
|
his phone_number view, he can REVOKE his access. Immediately any
|
|
|
|
|
access to the secretaries view will fail.
|
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
|
|
|
|
Someone might think that this rule by rule checking is a security
|
|
|
|
|
hole, but in fact it isn't. If this would not work, the secretary
|
|
|
|
|
could setup a table with the same columns as phone_number and
|
|
|
|
|
copy the data to there once per day. Then it's his own data and
|
|
|
|
|
he can grant access to everyone he wants. A GRANT means "I trust you".
|
|
|
|
|
If someone you trust does the thing above, it's time to
|
|
|
|
|
think it over and then REVOKE.
|
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
|
|
|
|
This mechanism does also work for update rules. In the examples
|
|
|
|
|
of the previous section, the owner of the tables in Al's database
|
|
|
|
|
could GRANT SELECT, INSERT, UPDATE and DELETE on the shoelace view to al.
|
|
|
|
|
But only SELECT on shoelace_log. The rule action to write log entries
|
2000-12-12 06:07:59 +01:00
|
|
|
|
will still be executed successfully. And Al could see the log entries.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
But he cannot create fake entries, nor could he manipulate or remove
|
|
|
|
|
existing ones.
|
|
|
|
|
|
|
|
|
|
<Note>
|
|
|
|
|
<Title>Warning</Title>
|
|
|
|
|
<Para>
|
|
|
|
|
GRANT ALL currently includes RULE permission. This means the granted
|
|
|
|
|
user could drop the rule, do the changes and reinstall it. I think
|
|
|
|
|
this should get changed quickly.
|
|
|
|
|
</Para>
|
|
|
|
|
</Note>
|
|
|
|
|
</Para>
|
|
|
|
|
</Sect1>
|
|
|
|
|
|
2000-09-29 22:21:34 +02:00
|
|
|
|
<Sect1 id="rules-triggers">
|
1998-10-25 02:29:01 +02:00
|
|
|
|
<Title>Rules versus Triggers</Title>
|
|
|
|
|
|
|
|
|
|
<Para>
|
|
|
|
|
Many things that can be done using triggers can also be
|
|
|
|
|
implemented using the <ProductName>Postgres</ProductName>
|
|
|
|
|
rule system. What currently cannot be implemented by
|
|
|
|
|
rules are some kinds of constraints. It is possible,
|
|
|
|
|
to place a qualified rule that rewrites a query to NOTHING
|
|
|
|
|
if the value of a column does not appear in another table.
|
|
|
|
|
But then the data is silently thrown away and that's
|
|
|
|
|
not a good idea. If checks for valid values are required,
|
|
|
|
|
and in the case of an invalid value an error message should
|
|
|
|
|
be generated, it must be done by a trigger for now.
|
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
|
|
|
|
On the other hand a trigger that is fired on INSERT
|
|
|
|
|
on a view can do the same as a rule, put the data somewhere
|
|
|
|
|
else and suppress the insert in the view. But it cannot
|
|
|
|
|
do the same thing on UPDATE or DELETE, because there is
|
|
|
|
|
no real data in the view relation that could be scanned
|
|
|
|
|
and thus the trigger would never get called. Only a rule
|
|
|
|
|
will help.
|
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
|
|
|
|
For the things that can be implemented by both,
|
|
|
|
|
it depends on the usage of the database, which is the best.
|
|
|
|
|
A trigger is fired for any row affected once. A rule manipulates
|
2001-09-10 23:58:47 +02:00
|
|
|
|
the parse tree or generates an additional one. So if many
|
1998-10-25 02:29:01 +02:00
|
|
|
|
rows are affected in one statement, a rule issuing one extra
|
|
|
|
|
query would usually do a better job than a trigger that is
|
|
|
|
|
called for any single row and must execute his operations
|
|
|
|
|
this many times.
|
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
|
|
|
|
For example: There are two tables
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
CREATE TABLE computer (
|
2001-09-12 03:22:25 +02:00
|
|
|
|
hostname text, -- indexed
|
1998-10-25 02:29:01 +02:00
|
|
|
|
manufacturer text -- indexed
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
CREATE TABLE software (
|
|
|
|
|
software text, -- indexed
|
|
|
|
|
hostname text -- indexed
|
|
|
|
|
);
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
Both tables have many
|
2001-09-10 23:58:47 +02:00
|
|
|
|
thousands of rows and the index on <structfield>hostname</> is unique.
|
|
|
|
|
The <structfield>hostname</> column contains the full qualified domain
|
1998-10-25 02:29:01 +02:00
|
|
|
|
name of the computer. The rule/trigger should constraint
|
|
|
|
|
delete rows from software that reference the deleted host.
|
|
|
|
|
Since the trigger is called for each individual row
|
|
|
|
|
deleted from computer, it can use the statement
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
DELETE FROM software WHERE hostname = $1;
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
2001-09-10 23:58:47 +02:00
|
|
|
|
in a prepared and saved plan and pass the <structfield>hostname</> in
|
1998-10-25 02:29:01 +02:00
|
|
|
|
the parameter. The rule would be written as
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
CREATE RULE computer_del AS ON DELETE TO computer
|
|
|
|
|
DO DELETE FROM software WHERE hostname = OLD.hostname;
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
Now we look at different types of deletes. In the case of a
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
DELETE FROM computer WHERE hostname = 'mypc.local.net';
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
the table computer is scanned by index (fast) and the
|
|
|
|
|
query issued by the trigger would also be an index scan (fast too).
|
|
|
|
|
The extra query from the rule would be a
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
DELETE FROM software WHERE computer.hostname = 'mypc.local.net'
|
|
|
|
|
AND software.hostname = computer.hostname;
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
2001-05-17 23:50:18 +02:00
|
|
|
|
Since there are appropriate indexes setup, the planner
|
1998-10-25 02:29:01 +02:00
|
|
|
|
will create a plan of
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
Nestloop
|
|
|
|
|
-> Index Scan using comp_hostidx on computer
|
|
|
|
|
-> Index Scan using soft_hostidx on software
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
So there would be not that much difference in speed between
|
|
|
|
|
the trigger and the rule implementation. With the next delete
|
2001-09-10 23:58:47 +02:00
|
|
|
|
we want to get rid of all the 2000 computers where the <structfield>hostname</> starts
|
1998-10-25 02:29:01 +02:00
|
|
|
|
with 'old'. There are two possible queries to do that. One is
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
DELETE FROM computer WHERE hostname >= 'old'
|
|
|
|
|
AND hostname < 'ole'
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
Where the plan for the rule query will be a
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
Hash Join
|
|
|
|
|
-> Seq Scan on software
|
|
|
|
|
-> Hash
|
|
|
|
|
-> Index Scan using comp_hostidx on computer
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
The other possible query is a
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
DELETE FROM computer WHERE hostname ~ '^old';
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
with the execution plan
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
Nestloop
|
|
|
|
|
-> Index Scan using comp_hostidx on computer
|
|
|
|
|
-> Index Scan using soft_hostidx on software
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
2000-12-12 06:07:59 +01:00
|
|
|
|
This shows, that the planner does not realize that the
|
2001-09-10 23:58:47 +02:00
|
|
|
|
qualification for the <structfield>hostname</> on computer could also be
|
1998-10-25 02:29:01 +02:00
|
|
|
|
used for an index scan on software when there are
|
|
|
|
|
multiple qualification expressions combined with AND, what
|
|
|
|
|
he does in the regexp version of the query. The trigger will
|
|
|
|
|
get invoked once for any of the 2000 old computers that
|
|
|
|
|
have to be deleted and that will result in one index scan
|
|
|
|
|
over computer and 2000 index scans for the software. The
|
2001-05-17 23:50:18 +02:00
|
|
|
|
rule implementation will do it with two queries over indexes.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
And it depends on the overall size of the software table if
|
2001-09-10 23:58:47 +02:00
|
|
|
|
the rule will still be faster in the sequential scan situation. 2000
|
1998-10-25 02:29:01 +02:00
|
|
|
|
query executions over the SPI manager take some time, even
|
|
|
|
|
if all the index blocks to look them up will soon appear in
|
|
|
|
|
the cache.
|
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
|
|
|
|
The last query we look at is a
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
DELETE FROM computer WHERE manufacurer = 'bim';
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
Again this could result in many rows to be deleted from
|
|
|
|
|
computer. So the trigger will again fire many queries into
|
2001-09-10 23:58:47 +02:00
|
|
|
|
the executor. But the rule plan will again be the nested loop over
|
|
|
|
|
two index scans. Only using another index on computer:
|
1998-10-25 02:29:01 +02:00
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
Nestloop
|
|
|
|
|
-> Index Scan using comp_manufidx on computer
|
|
|
|
|
-> Index Scan using soft_hostidx on software
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
resulting from the rules query
|
|
|
|
|
|
|
|
|
|
<ProgramListing>
|
|
|
|
|
DELETE FROM software WHERE computer.manufacurer = 'bim'
|
|
|
|
|
AND software.hostname = computer.hostname;
|
|
|
|
|
</ProgramListing>
|
|
|
|
|
|
|
|
|
|
In any of these cases, the extra queries from the rule system will be
|
|
|
|
|
more or less independent from the number of affected rows
|
|
|
|
|
in a query.
|
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
2000-05-02 22:02:03 +02:00
|
|
|
|
Another situation is cases on UPDATE where it depends on the
|
1998-10-25 02:29:01 +02:00
|
|
|
|
change of an attribute if an action should be performed or
|
|
|
|
|
not. In <ProductName>Postgres</ProductName> version 6.4, the
|
|
|
|
|
attribute specification for rule events is disabled (it will have
|
2000-12-12 06:07:59 +01:00
|
|
|
|
its comeback latest in 6.5, maybe earlier
|
1998-10-25 02:29:01 +02:00
|
|
|
|
- stay tuned). So for now the only way to
|
|
|
|
|
create a rule as in the shoelace_log example is to do it with
|
|
|
|
|
a rule qualification. That results in an extra query that is
|
2000-05-02 22:02:03 +02:00
|
|
|
|
performed always, even if the attribute of interest cannot
|
2001-09-10 23:58:47 +02:00
|
|
|
|
change at all because it does not appear in the target list
|
1998-10-25 02:29:01 +02:00
|
|
|
|
of the initial query. When this is enabled again, it will be
|
|
|
|
|
one more advantage of rules over triggers. Optimization of
|
|
|
|
|
a trigger must fail by definition in this case, because the
|
2000-12-12 06:07:59 +01:00
|
|
|
|
fact that its actions will only be done when a specific attribute
|
|
|
|
|
is updated is hidden in its functionality. The definition of
|
1998-10-25 02:29:01 +02:00
|
|
|
|
a trigger only allows to specify it on row level, so whenever a
|
2000-12-12 06:07:59 +01:00
|
|
|
|
row is touched, the trigger must be called to make its
|
1998-10-25 02:29:01 +02:00
|
|
|
|
decision. The rule system will know it by looking up the
|
2001-09-10 23:58:47 +02:00
|
|
|
|
target list and will suppress the additional query completely
|
1998-10-25 02:29:01 +02:00
|
|
|
|
if the attribute isn't touched. So the rule, qualified or not,
|
2000-05-02 22:02:03 +02:00
|
|
|
|
will only do its scans if there ever could be something to do.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
</Para>
|
|
|
|
|
|
|
|
|
|
<Para>
|
2000-12-12 06:07:59 +01:00
|
|
|
|
Rules will only be significantly slower than triggers if
|
1998-10-25 02:29:01 +02:00
|
|
|
|
their actions result in large and bad qualified joins, a situation
|
2000-12-12 06:07:59 +01:00
|
|
|
|
where the planner fails. They are a big hammer.
|
1998-10-25 02:29:01 +02:00
|
|
|
|
Using a big hammer without caution can cause big damage. But
|
|
|
|
|
used with the right touch, they can hit any nail on the head.
|
1998-04-28 16:52:46 +02:00
|
|
|
|
</Para>
|
1998-10-25 02:29:01 +02:00
|
|
|
|
</Sect1>
|
1998-04-28 16:52:46 +02:00
|
|
|
|
|
1998-03-01 09:16:16 +01:00
|
|
|
|
</Chapter>
|
2000-05-02 22:02:03 +02:00
|
|
|
|
|
|
|
|
|
<!-- Keep this comment at the end of the file
|
|
|
|
|
Local variables:
|
|
|
|
|
mode:sgml
|
|
|
|
|
sgml-omittag:nil
|
|
|
|
|
sgml-shorttag:t
|
|
|
|
|
sgml-minimize-attributes:nil
|
|
|
|
|
sgml-always-quote-attributes:t
|
|
|
|
|
sgml-indent-step:1
|
|
|
|
|
sgml-indent-data:t
|
|
|
|
|
sgml-parent-document:nil
|
|
|
|
|
sgml-default-dtd-file:"./reference.ced"
|
|
|
|
|
sgml-exposed-tags:nil
|
|
|
|
|
sgml-local-catalogs:("/usr/lib/sgml/catalog")
|
|
|
|
|
sgml-local-ecat-files:nil
|
|
|
|
|
End:
|
|
|
|
|
-->
|