Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 6e07709

Browse files
committed
Implement SQL-compliant treatment of row comparisons for < <= > >= cases
(previously we only did = and <> correctly). Also, allow row comparisons with any operators that are in btree opclasses, not only those with these specific names. This gets rid of a whole lot of indefensible assumptions about the behavior of particular operators based on their names ... though it's still true that IN and NOT IN expand to "= ANY". The patch adds a RowCompareExpr expression node type, and makes some changes in the representation of ANY/ALL/ROWCOMPARE SubLinks so that they can share code with RowCompareExpr. I have not yet done anything about making RowCompareExpr an indexable operator, but will look at that soon. initdb forced due to changes in stored rules.
1 parent a37422e commit 6e07709

File tree

26 files changed

+1442
-649
lines changed

26 files changed

+1442
-649
lines changed

doc/src/sgml/func.sgml

Lines changed: 93 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!--
2-
$PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.300 2005/12/21 23:22:55 momjian Exp $
2+
$PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.301 2005/12/28 01:29:58 tgl Exp $
33
PostgreSQL documentation
44
-->
55

@@ -350,18 +350,18 @@ PostgreSQL documentation
350350
</indexterm>
351351
The ordinary comparison operators yield null (signifying <quote>unknown</>)
352352
when either input is null. Another way to do comparisons is with the
353-
<literal>IS DISTINCT FROM</literal> construct:
353+
<literal>IS <optional> NOT </> DISTINCT FROM</literal> construct:
354354
<synopsis>
355355
<replaceable>expression</replaceable> IS DISTINCT FROM <replaceable>expression</replaceable>
356356
<replaceable>expression</replaceable> IS NOT DISTINCT FROM <replaceable>expression</replaceable>
357357
</synopsis>
358-
For non-null inputs, <literal>IS DISTINCT FROM</literal> this is
358+
For non-null inputs, <literal>IS DISTINCT FROM</literal> is
359359
the same as the <literal>&lt;&gt;</> operator. However, when both
360360
inputs are null it will return false, and when just one input is
361361
null it will return true. Similarly, <literal>IS NOT DISTINCT
362362
FROM</literal> is identical to <literal>=</literal> for non-null
363-
inputs, returns true when both inputs are null, and false
364-
otherwise. Thus, these constructs effectively act as though null
363+
inputs, but it returns true when both inputs are null, and false when only
364+
one input is null. Thus, these constructs effectively act as though null
365365
were a normal data value, rather than <quote>unknown</>.
366366
</para>
367367

@@ -7999,8 +7999,8 @@ SELECT col1 FROM tab1
79997999
equal if all their corresponding members are non-null and equal; the rows
80008000
are unequal if any corresponding members are non-null and unequal;
80018001
otherwise the result of that row comparison is unknown (null).
8002-
If all the row results are either unequal or null, with at least one null,
8003-
then the result of <token>IN</token> is null.
8002+
If all the per-row results are either unequal or null, with at least one
8003+
null, then the result of <token>IN</token> is null.
80048004
</para>
80058005
</sect2>
80068006

@@ -8055,8 +8055,8 @@ SELECT col1 FROM tab1
80558055
equal if all their corresponding members are non-null and equal; the rows
80568056
are unequal if any corresponding members are non-null and unequal;
80578057
otherwise the result of that row comparison is unknown (null).
8058-
If all the row results are either unequal or null, with at least one null,
8059-
then the result of <token>NOT IN</token> is null.
8058+
If all the per-row results are either unequal or null, with at least one
8059+
null, then the result of <token>NOT IN</token> is null.
80608060
</para>
80618061
</sect2>
80628062

@@ -8109,23 +8109,19 @@ SELECT col1 FROM tab1
81098109
subquery, which must return exactly as many columns as there are
81108110
expressions in the left-hand row. The left-hand expressions are
81118111
evaluated and compared row-wise to each row of the subquery result,
8112-
using the given <replaceable>operator</replaceable>. Presently,
8113-
only <literal>=</literal> and <literal>&lt;&gt;</literal> operators are allowed
8114-
in row-wise <token>ANY</token> constructs.
8115-
The result of <token>ANY</token> is <quote>true</> if any equal or unequal row is
8116-
found, respectively.
8117-
The result is <quote>false</> if no such row is found (including the special
8118-
case where the subquery returns no rows).
8112+
using the given <replaceable>operator</replaceable>.
8113+
The result of <token>ANY</token> is <quote>true</> if the comparison
8114+
returns true for any subquery row.
8115+
The result is <quote>false</> if the comparison returns false for every
8116+
subquery row (including the special case where the subquery returns no
8117+
rows).
8118+
The result is NULL if the comparison does not return true for any row,
8119+
and it returns NULL for at least one row.
81198120
</para>
81208121

81218122
<para>
8122-
As usual, null values in the rows are combined per
8123-
the normal rules of SQL Boolean expressions. Two rows are considered
8124-
equal if all their corresponding members are non-null and equal; the rows
8125-
are unequal if any corresponding members are non-null and unequal;
8126-
otherwise the result of that row comparison is unknown (null).
8127-
If there is at least one null row result, then the result of <token>ANY</token>
8128-
cannot be false; it will be true or null.
8123+
See <xref linkend="row-wise-comparison"> for details about the meaning
8124+
of a row-wise comparison.
81298125
</para>
81308126
</sect2>
81318127

@@ -8145,20 +8141,14 @@ SELECT col1 FROM tab1
81458141
The result of <token>ALL</token> is <quote>true</> if all rows yield true
81468142
(including the special case where the subquery returns no rows).
81478143
The result is <quote>false</> if any false result is found.
8144+
The result is NULL if the comparison does not return false for any row,
8145+
and it returns NULL for at least one row.
81488146
</para>
81498147

81508148
<para>
81518149
<token>NOT IN</token> is equivalent to <literal>&lt;&gt; ALL</literal>.
81528150
</para>
81538151

8154-
<para>
8155-
Note that if there are no failures but at least one right-hand row yields
8156-
null for the operator's result, the result of the <token>ALL</token> construct
8157-
will be null, not true.
8158-
This is in accordance with SQL's normal rules for Boolean combinations
8159-
of null values.
8160-
</para>
8161-
81628152
<para>
81638153
As with <token>EXISTS</token>, it's unwise to assume that the subquery will
81648154
be evaluated completely.
@@ -8175,24 +8165,19 @@ SELECT col1 FROM tab1
81758165
subquery, which must return exactly as many columns as there are
81768166
expressions in the left-hand row. The left-hand expressions are
81778167
evaluated and compared row-wise to each row of the subquery result,
8178-
using the given <replaceable>operator</replaceable>. Presently,
8179-
only <literal>=</literal> and <literal>&lt;&gt;</literal> operators are allowed
8180-
in row-wise <token>ALL</token> queries.
8181-
The result of <token>ALL</token> is <quote>true</> if all subquery rows are equal
8182-
or unequal, respectively (including the special
8168+
using the given <replaceable>operator</replaceable>.
8169+
The result of <token>ALL</token> is <quote>true</> if the comparison
8170+
returns true for all subquery rows (including the special
81838171
case where the subquery returns no rows).
8184-
The result is <quote>false</> if any row is found to be unequal or equal,
8185-
respectively.
8172+
The result is <quote>false</> if the comparison returns false for any
8173+
subquery row.
8174+
The result is NULL if the comparison does not return false for any
8175+
subquery row, and it returns NULL for at least one row.
81868176
</para>
81878177

81888178
<para>
8189-
As usual, null values in the rows are combined per
8190-
the normal rules of SQL Boolean expressions. Two rows are considered
8191-
equal if all their corresponding members are non-null and equal; the rows
8192-
are unequal if any corresponding members are non-null and unequal;
8193-
otherwise the result of that row comparison is unknown (null).
8194-
If there is at least one null row result, then the result of <token>ALL</token>
8195-
cannot be true; it will be false or null.
8179+
See <xref linkend="row-wise-comparison"> for details about the meaning
8180+
of a row-wise comparison.
81968181
</para>
81978182
</sect2>
81988183

@@ -8216,17 +8201,11 @@ SELECT col1 FROM tab1
82168201
the subquery cannot return more than one row. (If it returns zero rows,
82178202
the result is taken to be null.) The left-hand side is evaluated and
82188203
compared row-wise to the single subquery result row.
8219-
Presently, only <literal>=</literal> and <literal>&lt;&gt;</literal> operators are allowed
8220-
in row-wise comparisons.
8221-
The result is <quote>true</> if the two rows are equal or unequal, respectively.
82228204
</para>
82238205

82248206
<para>
8225-
As usual, null values in the rows are combined per
8226-
the normal rules of SQL Boolean expressions. Two rows are considered
8227-
equal if all their corresponding members are non-null and equal; the rows
8228-
are unequal if any corresponding members are non-null and unequal;
8229-
otherwise the result of the row comparison is unknown (null).
8207+
See <xref linkend="row-wise-comparison"> for details about the meaning
8208+
of a row-wise comparison.
82308209
</para>
82318210
</sect2>
82328211
</sect1>
@@ -8255,6 +8234,10 @@ SELECT col1 FROM tab1
82558234
<primary>SOME</primary>
82568235
</indexterm>
82578236

8237+
<indexterm>
8238+
<primary>row-wise comparison</primary>
8239+
</indexterm>
8240+
82588241
<indexterm>
82598242
<primary>comparison</primary>
82608243
<secondary>row-wise</secondary>
@@ -8264,6 +8247,10 @@ SELECT col1 FROM tab1
82648247
<primary>IS DISTINCT FROM</primary>
82658248
</indexterm>
82668249

8250+
<indexterm>
8251+
<primary>IS NOT DISTINCT FROM</primary>
8252+
</indexterm>
8253+
82678254
<indexterm>
82688255
<primary>IS NULL</primary>
82698256
</indexterm>
@@ -8288,7 +8275,7 @@ SELECT col1 FROM tab1
82888275
<title><literal>IN</literal></title>
82898276

82908277
<synopsis>
8291-
<replaceable>expression</replaceable> IN (<replaceable>value</replaceable><optional>, ...</optional>)
8278+
<replaceable>expression</replaceable> IN (<replaceable>value</replaceable> <optional>, ...</optional>)
82928279
</synopsis>
82938280

82948281
<para>
@@ -8319,7 +8306,7 @@ OR
83198306
<title><literal>NOT IN</literal></title>
83208307

83218308
<synopsis>
8322-
<replaceable>expression</replaceable> NOT IN (<replaceable>value</replaceable><optional>, ...</optional>)
8309+
<replaceable>expression</replaceable> NOT IN (<replaceable>value</replaceable> <optional>, ...</optional>)
83238310
</synopsis>
83248311

83258312
<para>
@@ -8425,7 +8412,7 @@ AND
84258412
</para>
84268413
</sect2>
84278414

8428-
<sect2>
8415+
<sect2 id="row-wise-comparison">
84298416
<title>Row-wise Comparison</title>
84308417

84318418
<synopsis>
@@ -8436,23 +8423,52 @@ AND
84368423
Each side is a row constructor,
84378424
as described in <xref linkend="sql-syntax-row-constructors">.
84388425
The two row values must have the same number of fields.
8439-
Each side is evaluated and they are compared row-wise.
8440-
Presently, only <literal>=</literal> and <literal>&lt;&gt;</literal> operators are allowed
8441-
in row-wise comparisons.
8442-
The result is <quote>true</> if the two rows are equal or unequal, respectively.
8426+
Each side is evaluated and they are compared row-wise. Row comparisons
8427+
are allowed when the <replaceable>operator</replaceable> is
8428+
<literal>=</>,
8429+
<literal>&lt;&gt;</>,
8430+
<literal>&lt;</>,
8431+
<literal>&lt;=</>,
8432+
<literal>&gt;</> or
8433+
<literal>&gt;=</>,
8434+
or has semantics similar to one of these. (To be specific, an operator
8435+
can be a row comparison operator if it is a member of a btree operator
8436+
class, or is the negator of the <literal>=</> member of a btree operator
8437+
class.)
84438438
</para>
84448439

84458440
<para>
8446-
As usual, null values in the rows are combined per
8447-
the normal rules of SQL Boolean expressions. Two rows are considered
8441+
The <literal>=</> and <literal>&lt;&gt;</> cases work slightly differently
8442+
from the others. Two rows are considered
84488443
equal if all their corresponding members are non-null and equal; the rows
84498444
are unequal if any corresponding members are non-null and unequal;
84508445
otherwise the result of the row comparison is unknown (null).
84518446
</para>
84528447

8453-
<indexterm>
8454-
<primary>IS DISTINCT FROM</primary>
8455-
</indexterm>
8448+
<para>
8449+
For the <literal>&lt;</>, <literal>&lt;=</>, <literal>&gt;</> and
8450+
<literal>&gt;=</> cases, the row elements are compared left-to-right,
8451+
stopping as soon as an unequal or null pair of elements is found.
8452+
If either of this pair of elements is null, the result of the
8453+
row comparison is unknown (null); otherwise comparison of this pair
8454+
of elements determines the result. For example,
8455+
<literal>ROW(1,2,NULL) &lt; ROW(1,3,0)</>
8456+
yields true, not null, because the third pair of elements are not
8457+
considered.
8458+
</para>
8459+
8460+
<note>
8461+
<para>
8462+
Prior to <productname>PostgreSQL</productname> 8.2, the
8463+
<literal>&lt;</>, <literal>&lt;=</>, <literal>&gt;</> and <literal>&gt;=</>
8464+
cases were not handled per SQL specification. A comparison like
8465+
<literal>ROW(a,b) &lt; ROW(c,d)</>
8466+
was implemented as
8467+
<literal>a &lt; c AND b &lt; d</>
8468+
whereas the correct behavior is equivalent to
8469+
<literal>a &lt; c OR (a = c AND b &lt; d)</>.
8470+
</para>
8471+
</note>
84568472

84578473
<synopsis>
84588474
<replaceable>row_constructor</replaceable> IS DISTINCT FROM <replaceable>row_constructor</replaceable>
@@ -8466,6 +8482,18 @@ AND
84668482
be either true or false, never null.
84678483
</para>
84688484

8485+
<synopsis>
8486+
<replaceable>row_constructor</replaceable> IS NOT DISTINCT FROM <replaceable>row_constructor</replaceable>
8487+
</synopsis>
8488+
8489+
<para>
8490+
This construct is similar to a <literal>=</literal> row comparison,
8491+
but it does not yield null for null inputs. Instead, any null value is
8492+
considered unequal to (distinct from) any non-null value, and any two
8493+
nulls are considered equal (not distinct). Thus the result will always
8494+
be either true or false, never null.
8495+
</para>
8496+
84698497
<synopsis>
84708498
<replaceable>row_constructor</replaceable> IS NULL
84718499
<replaceable>row_constructor</replaceable> IS NOT NULL

src/backend/catalog/dependency.c

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* Portions Copyright (c) 1994, Regents of the University of California
99
*
1010
* IDENTIFICATION
11-
* $PostgreSQL: pgsql/src/backend/catalog/dependency.c,v 1.48 2005/11/22 18:17:07 momjian Exp $
11+
* $PostgreSQL: pgsql/src/backend/catalog/dependency.c,v 1.49 2005/12/28 01:29:59 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -1129,23 +1129,28 @@ find_expr_references_walker(Node *node,
11291129
&context->addrs);
11301130
/* fall through to examine arguments */
11311131
}
1132-
if (IsA(node, SubLink))
1132+
if (is_subplan(node))
11331133
{
1134-
SubLink *sublink = (SubLink *) node;
1135-
ListCell *opid;
1134+
/* Extra work needed here if we ever need this case */
1135+
elog(ERROR, "already-planned subqueries not supported");
1136+
}
1137+
if (IsA(node, RowCompareExpr))
1138+
{
1139+
RowCompareExpr *rcexpr = (RowCompareExpr *) node;
1140+
ListCell *l;
11361141

1137-
foreach(opid, sublink->operOids)
1142+
foreach(l, rcexpr->opnos)
11381143
{
1139-
add_object_address(OCLASS_OPERATOR, lfirst_oid(opid), 0,
1144+
add_object_address(OCLASS_OPERATOR, lfirst_oid(l), 0,
1145+
&context->addrs);
1146+
}
1147+
foreach(l, rcexpr->opclasses)
1148+
{
1149+
add_object_address(OCLASS_OPCLASS, lfirst_oid(l), 0,
11401150
&context->addrs);
11411151
}
11421152
/* fall through to examine arguments */
11431153
}
1144-
if (is_subplan(node))
1145-
{
1146-
/* Extra work needed here if we ever need this case */
1147-
elog(ERROR, "already-planned subqueries not supported");
1148-
}
11491154
if (IsA(node, Query))
11501155
{
11511156
/* Recurse into RTE subquery or not-yet-planned sublink subquery */

0 commit comments

Comments
 (0)