Re: BUG #17233: Incorrect behavior of DELETE command with bad subquery in WHERE clause

Поиск
Список
Период
Сортировка
От Tom Lane
Тема Re: BUG #17233: Incorrect behavior of DELETE command with bad subquery in WHERE clause
Дата
Msg-id 2475055.1667588822@sss.pgh.pa.us
обсуждение исходный текст
Ответ на Re: BUG #17233: Incorrect behavior of DELETE command with bad subquery in WHERE clause  (Alvaro Herrera <alvherre@alvh.no-ip.org>)
Ответы Re: BUG #17233: Incorrect behavior of DELETE command with bad subquery in WHERE clause
Список pgsql-bugs
Alvaro Herrera <alvherre@alvh.no-ip.org> writes:
> I did wonder why errorMissingColumn doesn't consider rte_visible_if_* in
> the case when there *is* an rsecond candidate.  I understand that the
> reason is that if we come across any exact match we already return that
> one without looking for a second one.  Maybe this deserves a comment (in
> errorMissingColumn I mean) but I also wonder if we shouldn't scan the
> whole RT in case there's another exact match that's also not visible.

Um.  I'd not wanted to touch the fuzzy-search stuff because it seemed
like a mess of incomprehensible (if not actually buggy) code.  But you
have a point --- I'd already noticed that the code was encouraging
people to qualify with a name that might be the wrong table altogether.

So here's a revision that tries to clean that up a little.  0001 is the
same patch as before, and then 0002 revises the fuzzy-search logic enough
that I can make sense of it.  I split them mainly so that you can see the
behavioral difference in the changed test outputs.

            regards, tom lane

diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 81f9ae2f02..6290ce9b43 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -81,6 +81,8 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
                             int location, bool include_dropped,
                             List **colnames, List **colvars);
 static int    specialAttNum(const char *attname);
+static bool rte_visible_if_lateral(ParseState *pstate, RangeTblEntry *rte);
+static bool rte_visible_if_qualified(ParseState *pstate, RangeTblEntry *rte);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);


@@ -3603,17 +3605,27 @@ errorMissingRTE(ParseState *pstate, RangeVar *relation)
             badAlias = rte->eref->aliasname;
     }

-    if (rte)
+    /* If it looks like the user forgot to use an alias, hint about that */
+    if (badAlias)
         ereport(ERROR,
                 (errcode(ERRCODE_UNDEFINED_TABLE),
                  errmsg("invalid reference to FROM-clause entry for table \"%s\"",
                         relation->relname),
-                 (badAlias ?
-                  errhint("Perhaps you meant to reference the table alias \"%s\".",
-                          badAlias) :
-                  errhint("There is an entry for table \"%s\", but it cannot be referenced from this part of the
query.",
-                          rte->eref->aliasname)),
+                 errhint("Perhaps you meant to reference the table alias \"%s\".",
+                         badAlias),
                  parser_errposition(pstate, relation->location)));
+    /* Hint about case where we found an (inaccessible) exact match */
+    else if (rte)
+        ereport(ERROR,
+                (errcode(ERRCODE_UNDEFINED_TABLE),
+                 errmsg("invalid reference to FROM-clause entry for table \"%s\"",
+                        relation->relname),
+                 errdetail("There is an entry for table \"%s\", but it cannot be referenced from this part of the
query.",
+                           rte->eref->aliasname),
+                 rte_visible_if_lateral(pstate, rte) ?
+                 errhint("To reference that table, you must mark this subquery with LATERAL.") : 0,
+                 parser_errposition(pstate, relation->location)));
+    /* Else, we have nothing to offer but the bald statement of error */
     else
         ereport(ERROR,
                 (errcode(ERRCODE_UNDEFINED_TABLE),
@@ -3638,9 +3650,6 @@ errorMissingColumn(ParseState *pstate,
     /*
      * Search the entire rtable looking for possible matches.  If we find one,
      * emit a hint about it.
-     *
-     * TODO: improve this code (and also errorMissingRTE) to mention using
-     * LATERAL if appropriate.
      */
     state = searchRangeTableForCol(pstate, relname, colname, location);

@@ -3659,21 +3668,38 @@ errorMissingColumn(ParseState *pstate,

     if (!state->rsecond)
     {
-        /*
-         * Handle case where there is zero or one column suggestions to hint,
-         * including exact matches referenced but not visible.
-         */
-        ereport(ERROR,
-                (errcode(ERRCODE_UNDEFINED_COLUMN),
-                 relname ?
-                 errmsg("column %s.%s does not exist", relname, colname) :
-                 errmsg("column \"%s\" does not exist", colname),
-                 state->rfirst ? closestfirst ?
-                 errhint("Perhaps you meant to reference the column \"%s.%s\".",
-                         state->rfirst->eref->aliasname, closestfirst) :
-                 errhint("There is a column named \"%s\" in table \"%s\", but it cannot be referenced from this part
ofthe query.", 
-                         colname, state->rfirst->eref->aliasname) : 0,
-                 parser_errposition(pstate, location)));
+        /* If we found no match at all, we have little to report */
+        if (!state->rfirst)
+            ereport(ERROR,
+                    (errcode(ERRCODE_UNDEFINED_COLUMN),
+                     relname ?
+                     errmsg("column %s.%s does not exist", relname, colname) :
+                     errmsg("column \"%s\" does not exist", colname),
+                     parser_errposition(pstate, location)));
+        /* Handle case where we have a single alternative spelling to offer */
+        else if (closestfirst)
+            ereport(ERROR,
+                    (errcode(ERRCODE_UNDEFINED_COLUMN),
+                     relname ?
+                     errmsg("column %s.%s does not exist", relname, colname) :
+                     errmsg("column \"%s\" does not exist", colname),
+                     errhint("Perhaps you meant to reference the column \"%s.%s\".",
+                             state->rfirst->eref->aliasname, closestfirst),
+                     parser_errposition(pstate, location)));
+        /* We found an exact match but it's inaccessible for some reason */
+        else
+            ereport(ERROR,
+                    (errcode(ERRCODE_UNDEFINED_COLUMN),
+                     relname ?
+                     errmsg("column %s.%s does not exist", relname, colname) :
+                     errmsg("column \"%s\" does not exist", colname),
+                     errdetail("There is a column named \"%s\" in table \"%s\", but it cannot be referenced from this
partof the query.", 
+                               colname, state->rfirst->eref->aliasname),
+                     rte_visible_if_lateral(pstate, state->rfirst) ?
+                     errhint("To reference that column, you must mark this subquery with LATERAL.") :
+                     (!relname && rte_visible_if_qualified(pstate, state->rfirst)) ?
+                     errhint("To reference that column, you must use a table-qualified name.") : 0,
+                     parser_errposition(pstate, location)));
     }
     else
     {
@@ -3695,6 +3721,71 @@ errorMissingColumn(ParseState *pstate,
     }
 }

+/*
+ * Find ParseNamespaceItem for RTE, if it's visible at all.
+ * We assume an RTE couldn't appear more than once in the namespace lists.
+ */
+static ParseNamespaceItem *
+findNSItemForRTE(ParseState *pstate, RangeTblEntry *rte)
+{
+    while (pstate != NULL)
+    {
+        ListCell   *l;
+
+        foreach(l, pstate->p_namespace)
+        {
+            ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(l);
+
+            if (nsitem->p_rte == rte)
+                return nsitem;
+        }
+        pstate = pstate->parentParseState;
+    }
+    return NULL;
+}
+
+/*
+ * Would this RTE be visible, if only the user had written LATERAL?
+ *
+ * This is a helper for deciding whether to issue a HINT about LATERAL.
+ * As such, it doesn't need to be 100% accurate; the HINT could be useful
+ * even if it's not quite right.  Hence, we don't delve into fine points
+ * about whether a found nsitem has the appropriate one of p_rel_visible or
+ * p_cols_visible set.
+ */
+static bool
+rte_visible_if_lateral(ParseState *pstate, RangeTblEntry *rte)
+{
+    ParseNamespaceItem *nsitem;
+
+    /* If LATERAL *is* active, we're clearly barking up the wrong tree */
+    if (pstate->p_lateral_active)
+        return false;
+    nsitem = findNSItemForRTE(pstate, rte);
+    if (nsitem)
+    {
+        /* Found it, report whether it's LATERAL-only */
+        return nsitem->p_lateral_only && nsitem->p_lateral_ok;
+    }
+    return false;
+}
+
+/*
+ * Would columns in this RTE be visible if qualified?
+ */
+static bool
+rte_visible_if_qualified(ParseState *pstate, RangeTblEntry *rte)
+{
+    ParseNamespaceItem *nsitem = findNSItemForRTE(pstate, rte);
+
+    if (nsitem)
+    {
+        /* Found it, report whether it's relation-only */
+        return nsitem->p_rel_visible && !nsitem->p_cols_visible;
+    }
+    return false;
+}
+

 /*
  * Examine a fully-parsed query, and return true iff any relation underlying
diff --git a/src/test/regress/expected/insert_conflict.out b/src/test/regress/expected/insert_conflict.out
index 66d8633e3e..9e9e3bd00c 100644
--- a/src/test/regress/expected/insert_conflict.out
+++ b/src/test/regress/expected/insert_conflict.out
@@ -242,7 +242,7 @@ insert into insertconflicttest values (1, 'Apple') on conflict (key) do update s
 ERROR:  invalid reference to FROM-clause entry for table "excluded"
 LINE 1: ...y) do update set fruit = excluded.fruit RETURNING excluded.f...
                                                              ^
-HINT:  There is an entry for table "excluded", but it cannot be referenced from this part of the query.
+DETAIL:  There is an entry for table "excluded", but it cannot be referenced from this part of the query.
 -- Only suggest <table>.* column when inference element misspelled:
 insert into insertconflicttest values (1, 'Apple') on conflict (keyy) do update set fruit = excluded.fruit;
 ERROR:  column "keyy" does not exist
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index b901d7299f..ebaa289e12 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -1638,7 +1638,7 @@ SELECT * FROM (J1_TBL JOIN J2_TBL USING (i)) AS x WHERE J1_TBL.t = 'one';  -- er
 ERROR:  invalid reference to FROM-clause entry for table "j1_tbl"
 LINE 1: ... * FROM (J1_TBL JOIN J2_TBL USING (i)) AS x WHERE J1_TBL.t =...
                                                              ^
-HINT:  There is an entry for table "j1_tbl", but it cannot be referenced from this part of the query.
+DETAIL:  There is an entry for table "j1_tbl", but it cannot be referenced from this part of the query.
 SELECT * FROM J1_TBL JOIN J2_TBL USING (i) AS x WHERE x.i = 1;  -- ok
  i | j |  t  | k
 ---+---+-----+----
@@ -5036,7 +5036,7 @@ select * from
 ERROR:  invalid reference to FROM-clause entry for table "y"
 LINE 2: ...bl x join (int4_tbl x cross join int4_tbl y) j on q1 = y.f1;
                                                                   ^
-HINT:  There is an entry for table "y", but it cannot be referenced from this part of the query.
+DETAIL:  There is an entry for table "y", but it cannot be referenced from this part of the query.
 select * from
   int8_tbl x join (int4_tbl x cross join int4_tbl y(ff)) j on q1 = f1; -- ok
  q1 | q2 | f1 | ff
@@ -5064,6 +5064,13 @@ ERROR:  column "uunique1" does not exist
 LINE 1: select uunique1 from
                ^
 HINT:  Perhaps you meant to reference the column "t1.unique1" or the column "t2.unique1".
+select ctid from
+  tenk1 t1 join tenk2 t2 on t1.two = t2.two; -- error, need qualification
+ERROR:  column "ctid" does not exist
+LINE 1: select ctid from
+               ^
+DETAIL:  There is a column named "ctid" in table "t1", but it cannot be referenced from this part of the query.
+HINT:  To reference that column, you must use a table-qualified name.
 --
 -- Take care to reference the correct RTE
 --
@@ -6097,22 +6104,26 @@ select f1,g from int4_tbl a, (select f1 as g) ss;
 ERROR:  column "f1" does not exist
 LINE 1: select f1,g from int4_tbl a, (select f1 as g) ss;
                                              ^
-HINT:  There is a column named "f1" in table "a", but it cannot be referenced from this part of the query.
+DETAIL:  There is a column named "f1" in table "a", but it cannot be referenced from this part of the query.
+HINT:  To reference that column, you must mark this subquery with LATERAL.
 select f1,g from int4_tbl a, (select a.f1 as g) ss;
 ERROR:  invalid reference to FROM-clause entry for table "a"
 LINE 1: select f1,g from int4_tbl a, (select a.f1 as g) ss;
                                              ^
-HINT:  There is an entry for table "a", but it cannot be referenced from this part of the query.
+DETAIL:  There is an entry for table "a", but it cannot be referenced from this part of the query.
+HINT:  To reference that table, you must mark this subquery with LATERAL.
 select f1,g from int4_tbl a cross join (select f1 as g) ss;
 ERROR:  column "f1" does not exist
 LINE 1: select f1,g from int4_tbl a cross join (select f1 as g) ss;
                                                        ^
-HINT:  There is a column named "f1" in table "a", but it cannot be referenced from this part of the query.
+DETAIL:  There is a column named "f1" in table "a", but it cannot be referenced from this part of the query.
+HINT:  To reference that column, you must mark this subquery with LATERAL.
 select f1,g from int4_tbl a cross join (select a.f1 as g) ss;
 ERROR:  invalid reference to FROM-clause entry for table "a"
 LINE 1: select f1,g from int4_tbl a cross join (select a.f1 as g) ss...
                                                        ^
-HINT:  There is an entry for table "a", but it cannot be referenced from this part of the query.
+DETAIL:  There is an entry for table "a", but it cannot be referenced from this part of the query.
+HINT:  To reference that table, you must mark this subquery with LATERAL.
 -- SQL:2008 says the left table is in scope but illegal to access here
 select f1,g from int4_tbl a right join lateral generate_series(0, a.f1) g on true;
 ERROR:  invalid reference to FROM-clause entry for table "a"
@@ -6142,12 +6153,12 @@ update xx1 set x2 = f1 from (select * from int4_tbl where f1 = x1) ss;
 ERROR:  column "x1" does not exist
 LINE 1: ... set x2 = f1 from (select * from int4_tbl where f1 = x1) ss;
                                                                 ^
-HINT:  There is a column named "x1" in table "xx1", but it cannot be referenced from this part of the query.
+DETAIL:  There is a column named "x1" in table "xx1", but it cannot be referenced from this part of the query.
 update xx1 set x2 = f1 from (select * from int4_tbl where f1 = xx1.x1) ss;
 ERROR:  invalid reference to FROM-clause entry for table "xx1"
 LINE 1: ...t x2 = f1 from (select * from int4_tbl where f1 = xx1.x1) ss...
                                                              ^
-HINT:  There is an entry for table "xx1", but it cannot be referenced from this part of the query.
+DETAIL:  There is an entry for table "xx1", but it cannot be referenced from this part of the query.
 -- can't do it even with LATERAL:
 update xx1 set x2 = f1 from lateral (select * from int4_tbl where f1 = x1) ss;
 ERROR:  invalid reference to FROM-clause entry for table "xx1"
@@ -6162,12 +6173,12 @@ delete from xx1 using (select * from int4_tbl where f1 = x1) ss;
 ERROR:  column "x1" does not exist
 LINE 1: ...te from xx1 using (select * from int4_tbl where f1 = x1) ss;
                                                                 ^
-HINT:  There is a column named "x1" in table "xx1", but it cannot be referenced from this part of the query.
+DETAIL:  There is a column named "x1" in table "xx1", but it cannot be referenced from this part of the query.
 delete from xx1 using (select * from int4_tbl where f1 = xx1.x1) ss;
 ERROR:  invalid reference to FROM-clause entry for table "xx1"
 LINE 1: ...from xx1 using (select * from int4_tbl where f1 = xx1.x1) ss...
                                                              ^
-HINT:  There is an entry for table "xx1", but it cannot be referenced from this part of the query.
+DETAIL:  There is an entry for table "xx1", but it cannot be referenced from this part of the query.
 delete from xx1 using lateral (select * from int4_tbl where f1 = x1) ss;
 ERROR:  invalid reference to FROM-clause entry for table "xx1"
 LINE 1: ...xx1 using lateral (select * from int4_tbl where f1 = x1) ss;
diff --git a/src/test/regress/expected/merge.out b/src/test/regress/expected/merge.out
index 787af41dfe..4bc0ad6b09 100644
--- a/src/test/regress/expected/merge.out
+++ b/src/test/regress/expected/merge.out
@@ -197,7 +197,7 @@ WHEN NOT MATCHED THEN
 ERROR:  invalid reference to FROM-clause entry for table "t"
 LINE 2: USING (SELECT * FROM source WHERE t.tid > sid) s
                                           ^
-HINT:  There is an entry for table "t", but it cannot be referenced from this part of the query.
+DETAIL:  There is an entry for table "t", but it cannot be referenced from this part of the query.
 --
 -- initial tests
 --
@@ -618,7 +618,7 @@ WHEN NOT MATCHED THEN
 ERROR:  invalid reference to FROM-clause entry for table "t"
 LINE 5:  INSERT (tid, balance) VALUES (t.tid, s.delta);
                                        ^
-HINT:  There is an entry for table "t", but it cannot be referenced from this part of the query.
+DETAIL:  There is an entry for table "t", but it cannot be referenced from this part of the query.
 -- and again with a constant ON clause
 BEGIN;
 MERGE INTO target t
@@ -629,7 +629,7 @@ WHEN NOT MATCHED THEN
 ERROR:  invalid reference to FROM-clause entry for table "t"
 LINE 5:  INSERT (tid, balance) VALUES (t.tid, s.delta);
                                        ^
-HINT:  There is an entry for table "t", but it cannot be referenced from this part of the query.
+DETAIL:  There is an entry for table "t", but it cannot be referenced from this part of the query.
 SELECT * FROM target ORDER BY tid;
 ERROR:  current transaction is aborted, commands ignored until end of transaction block
 ROLLBACK;
@@ -724,7 +724,7 @@ WHEN NOT MATCHED AND t.balance = 100 THEN
 ERROR:  invalid reference to FROM-clause entry for table "t"
 LINE 3: WHEN NOT MATCHED AND t.balance = 100 THEN
                              ^
-HINT:  There is an entry for table "t", but it cannot be referenced from this part of the query.
+DETAIL:  There is an entry for table "t", but it cannot be referenced from this part of the query.
 SELECT * FROM wq_target;
 ERROR:  current transaction is aborted, commands ignored until end of transaction block
 ROLLBACK;
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 624d0e5aae..acf28ba580 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1194,7 +1194,8 @@ do instead insert into rules_foo2 values (f1);
 ERROR:  column "f1" does not exist
 LINE 2: do instead insert into rules_foo2 values (f1);
                                                   ^
-HINT:  There is a column named "f1" in table "old", but it cannot be referenced from this part of the query.
+DETAIL:  There is a column named "f1" in table "old", but it cannot be referenced from this part of the query.
+HINT:  To reference that column, you must use a table-qualified name.
 -- this is the correct way:
 create rule rules_foorule as on insert to rules_foo where f1 < 100
 do instead insert into rules_foo2 values (new.f1);
diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out
index dece7310cf..e2613d6777 100644
--- a/src/test/regress/expected/union.out
+++ b/src/test/regress/expected/union.out
@@ -910,7 +910,7 @@ SELECT q1 FROM int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1;
 ERROR:  column "q2" does not exist
 LINE 1: ... int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1...
                                                              ^
-HINT:  There is a column named "q2" in table "*SELECT* 2", but it cannot be referenced from this part of the query.
+DETAIL:  There is a column named "q2" in table "*SELECT* 2", but it cannot be referenced from this part of the query.
 -- But this should work:
 SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1))) ORDER BY 1;
         q1
diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql
index ccbbe5454c..961ff7d103 100644
--- a/src/test/regress/sql/join.sql
+++ b/src/test/regress/sql/join.sql
@@ -1828,6 +1828,8 @@ select t2.uunique1 from
   tenk1 t1 join tenk2 t2 on t1.two = t2.two; -- error, prefer "t2" suggestion
 select uunique1 from
   tenk1 t1 join tenk2 t2 on t1.two = t2.two; -- error, suggest both at once
+select ctid from
+  tenk1 t1 join tenk2 t2 on t1.two = t2.two; -- error, need qualification

 --
 -- Take care to reference the correct RTE
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 6290ce9b43..4665f0b2b7 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -41,17 +41,35 @@
 /*
  * Support for fuzzily matching columns.
  *
- * This is for building diagnostic messages, where non-exact matching
- * attributes are suggested to the user.  The struct's fields may be facets of
- * a particular RTE, or of an entire range table, depending on context.
+ * This is for building diagnostic messages, where multiple or non-exact
+ * matching attributes are of interest.
+ *
+ * "distance" is the current best fuzzy-match distance if rfirst isn't NULL,
+ * otherwise it is the maximum acceptable distance plus 1.
+ *
+ * rfirst/first record the closest non-exact match so far, and distance
+ * is its distance from the target name.  If we have found a second non-exact
+ * match of exactly the same distance, rsecond/second record that.  (If
+ * we find three of the same distance, we conclude that "distance" is not
+ * a tight enough bound for a useful hint and clear rfirst/rsecond again.
+ * Only if we later find something closer will we re-populate rfirst.)
+ *
+ * rexact1/exact1 record the location of the first exactly-matching column,
+ * if any.  If we find multiple exact matches then rexact2/exact2 record
+ * another one (we don't especially care which).  Currently, these get
+ * populated independently of the fuzzy-match fields.
  */
 typedef struct
 {
-    int            distance;        /* Weighted distance (lowest so far) */
-    RangeTblEntry *rfirst;        /* RTE of first */
-    AttrNumber    first;            /* Closest attribute so far */
-    RangeTblEntry *rsecond;        /* RTE of second */
-    AttrNumber    second;            /* Second closest attribute so far */
+    int            distance;        /* Current or limit distance */
+    RangeTblEntry *rfirst;        /* RTE of closest non-exact match, or NULL */
+    AttrNumber    first;            /* Col index in rfirst */
+    RangeTblEntry *rsecond;        /* RTE of another non-exact match w/same dist */
+    AttrNumber    second;            /* Col index in rsecond */
+    RangeTblEntry *rexact1;        /* RTE of first exact match, or NULL */
+    AttrNumber    exact1;            /* Col index in rexact1 */
+    RangeTblEntry *rexact2;        /* RTE of second exact match, or NULL */
+    AttrNumber    exact2;            /* Col index in rexact2 */
 } FuzzyAttrMatchState;

 #define MAX_FUZZY_DISTANCE                3
@@ -612,47 +630,39 @@ updateFuzzyAttrMatchState(int fuzzy_rte_penalty,
      */
     if (columndistance < fuzzystate->distance)
     {
-        /* Store new lowest observed distance for RTE */
+        /* Store new lowest observed distance as first/only match */
         fuzzystate->distance = columndistance;
         fuzzystate->rfirst = rte;
         fuzzystate->first = attnum;
         fuzzystate->rsecond = NULL;
-        fuzzystate->second = InvalidAttrNumber;
     }
     else if (columndistance == fuzzystate->distance)
     {
-        /*
-         * This match distance may equal a prior match within this same range
-         * table.  When that happens, the prior match may also be given, but
-         * only if there is no more than two equally distant matches from the
-         * RTE (in turn, our caller will only accept two equally distant
-         * matches overall).
-         */
-        if (AttributeNumberIsValid(fuzzystate->second))
+        /* If we already have a match of this distance, update state */
+        if (fuzzystate->rsecond != NULL)
         {
-            /* Too many RTE-level matches */
+            /*
+             * Too many matches at same distance.  Clearly, this value of
+             * distance is too low a bar, so drop these entries while keeping
+             * the current distance value, so that only smaller distances will
+             * be considered interesting.  Only if we find something of lower
+             * distance will we re-populate rfirst (via the stanza above).
+             */
             fuzzystate->rfirst = NULL;
-            fuzzystate->first = InvalidAttrNumber;
             fuzzystate->rsecond = NULL;
-            fuzzystate->second = InvalidAttrNumber;
-            /* Clearly, distance is too low a bar (for *any* RTE) */
-            fuzzystate->distance = columndistance - 1;
         }
-        else if (AttributeNumberIsValid(fuzzystate->first))
+        else if (fuzzystate->rfirst != NULL)
         {
-            /* Record as provisional second match for RTE */
+            /* Record as provisional second match */
             fuzzystate->rsecond = rte;
             fuzzystate->second = attnum;
         }
-        else if (fuzzystate->distance <= MAX_FUZZY_DISTANCE)
+        else
         {
             /*
-             * Record as provisional first match (this can occasionally occur
-             * because previous lowest distance was "too low a bar", rather
-             * than being associated with a real match)
+             * Do nothing.  When rfirst is NULL, distance is more than what we
+             * want to consider acceptable, so we should ignore this match.
              */
-            fuzzystate->rfirst = rte;
-            fuzzystate->first = attnum;
         }
     }
 }
@@ -925,21 +935,15 @@ colNameToVar(ParseState *pstate, const char *colname, bool localonly,
  * This is different from colNameToVar in that it considers every entry in
  * the ParseState's rangetable(s), not only those that are currently visible
  * in the p_namespace list(s).  This behavior is invalid per the SQL spec,
- * and it may give ambiguous results (there might be multiple equally valid
- * matches, but only one will be returned).  This must be used ONLY as a
- * heuristic in giving suitable error messages.  See errorMissingColumn.
+ * and it may give ambiguous results (since there might be multiple equally
+ * valid matches).  This must be used ONLY as a heuristic in giving suitable
+ * error messages.  See errorMissingColumn.
  *
  * This function is also different in that it will consider approximate
  * matches -- if the user entered an alias/column pair that is only slightly
  * different from a valid pair, we may be able to infer what they meant to
- * type and provide a reasonable hint.
- *
- * The FuzzyAttrMatchState will have 'rfirst' pointing to the best RTE
- * containing the most promising match for the alias and column name.  If
- * the alias and column names match exactly, 'first' will be InvalidAttrNumber;
- * otherwise, it will be the attribute number for the match.  In the latter
- * case, 'rsecond' may point to a second, equally close approximate match,
- * and 'second' will contain the attribute number for the second match.
+ * type and provide a reasonable hint.  We return a FuzzyAttrMatchState
+ * struct providing information about both exact and approximate matches.
  */
 static FuzzyAttrMatchState *
 searchRangeTableForCol(ParseState *pstate, const char *alias, const char *colname,
@@ -951,8 +955,8 @@ searchRangeTableForCol(ParseState *pstate, const char *alias, const char *colnam
     fuzzystate->distance = MAX_FUZZY_DISTANCE + 1;
     fuzzystate->rfirst = NULL;
     fuzzystate->rsecond = NULL;
-    fuzzystate->first = InvalidAttrNumber;
-    fuzzystate->second = InvalidAttrNumber;
+    fuzzystate->rexact1 = NULL;
+    fuzzystate->rexact2 = NULL;

     while (pstate != NULL)
     {
@@ -962,6 +966,7 @@ searchRangeTableForCol(ParseState *pstate, const char *alias, const char *colnam
         {
             RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
             int            fuzzy_rte_penalty = 0;
+            int            attnum;

             /*
              * Typically, it is not useful to look for matches within join
@@ -988,18 +993,27 @@ searchRangeTableForCol(ParseState *pstate, const char *alias, const char *colnam
                                                   true);

             /*
-             * Scan for a matching column; if we find an exact match, we're
-             * done.  Otherwise, update fuzzystate.
+             * Scan for a matching column, and update fuzzystate.  Non-exact
+             * matches are dealt with inside scanRTEForColumn, but exact
+             * matches are handled here.  (There won't be more than one exact
+             * match in the same RTE, else we'd have thrown error earlier.)
              */
-            if (scanRTEForColumn(orig_pstate, rte, rte->eref, colname, location,
-                                 fuzzy_rte_penalty, fuzzystate)
-                && fuzzy_rte_penalty == 0)
+            attnum = scanRTEForColumn(orig_pstate, rte, rte->eref,
+                                      colname, location,
+                                      fuzzy_rte_penalty, fuzzystate);
+            if (attnum != InvalidAttrNumber && fuzzy_rte_penalty == 0)
             {
-                fuzzystate->rfirst = rte;
-                fuzzystate->first = InvalidAttrNumber;
-                fuzzystate->rsecond = NULL;
-                fuzzystate->second = InvalidAttrNumber;
-                return fuzzystate;
+                if (fuzzystate->rexact1 == NULL)
+                {
+                    fuzzystate->rexact1 = rte;
+                    fuzzystate->exact1 = attnum;
+                }
+                else
+                {
+                    /* Needn't worry about overwriting previous rexact2 */
+                    fuzzystate->rexact2 = rte;
+                    fuzzystate->exact2 = attnum;
+                }
             }
         }

@@ -3645,7 +3659,6 @@ errorMissingColumn(ParseState *pstate,
                    const char *relname, const char *colname, int location)
 {
     FuzzyAttrMatchState *state;
-    char       *closestfirst = NULL;

     /*
      * Search the entire rtable looking for possible matches.  If we find one,
@@ -3654,69 +3667,78 @@ errorMissingColumn(ParseState *pstate,
     state = searchRangeTableForCol(pstate, relname, colname, location);

     /*
-     * Extract closest col string for best match, if any.
-     *
-     * Infer an exact match referenced despite not being visible from the fact
-     * that an attribute number was not present in state passed back -- this
-     * is what is reported when !closestfirst.  There might also be an exact
-     * match that was qualified with an incorrect alias, in which case
-     * closestfirst will be set (so hint is the same as generic fuzzy case).
+     * If there are exact match(es), they must be inaccessible for some
+     * reason.
      */
-    if (state->rfirst && AttributeNumberIsValid(state->first))
-        closestfirst = strVal(list_nth(state->rfirst->eref->colnames,
-                                       state->first - 1));
-
-    if (!state->rsecond)
+    if (state->rexact1)
     {
-        /* If we found no match at all, we have little to report */
-        if (!state->rfirst)
-            ereport(ERROR,
-                    (errcode(ERRCODE_UNDEFINED_COLUMN),
-                     relname ?
-                     errmsg("column %s.%s does not exist", relname, colname) :
-                     errmsg("column \"%s\" does not exist", colname),
-                     parser_errposition(pstate, location)));
-        /* Handle case where we have a single alternative spelling to offer */
-        else if (closestfirst)
+        /*
+         * We don't try too hard when there's multiple inaccessible exact
+         * matches, but at least be sure that we don't misleadingly suggest
+         * that there's only one.
+         */
+        if (state->rexact2)
             ereport(ERROR,
                     (errcode(ERRCODE_UNDEFINED_COLUMN),
                      relname ?
                      errmsg("column %s.%s does not exist", relname, colname) :
                      errmsg("column \"%s\" does not exist", colname),
-                     errhint("Perhaps you meant to reference the column \"%s.%s\".",
-                             state->rfirst->eref->aliasname, closestfirst),
+                     errdetail("There are columns named \"%s\", but they are in tables that cannot be referenced from
thispart of the query.", 
+                               colname),
+                     !relname ? errhint("Try using a table-qualified name.") : 0,
                      parser_errposition(pstate, location)));
-        /* We found an exact match but it's inaccessible for some reason */
-        else
+        /* Single exact match, so try to determine why it's inaccessible. */
+        ereport(ERROR,
+                (errcode(ERRCODE_UNDEFINED_COLUMN),
+                 relname ?
+                 errmsg("column %s.%s does not exist", relname, colname) :
+                 errmsg("column \"%s\" does not exist", colname),
+                 errdetail("There is a column named \"%s\" in table \"%s\", but it cannot be referenced from this part
ofthe query.", 
+                           colname, state->rexact1->eref->aliasname),
+                 rte_visible_if_lateral(pstate, state->rexact1) ?
+                 errhint("To reference that column, you must mark this subquery with LATERAL.") :
+                 (!relname && rte_visible_if_qualified(pstate, state->rexact1)) ?
+                 errhint("To reference that column, you must use a table-qualified name.") : 0,
+                 parser_errposition(pstate, location)));
+    }
+
+    if (!state->rsecond)
+    {
+        /* If we found no match at all, we have little to report */
+        if (!state->rfirst)
             ereport(ERROR,
                     (errcode(ERRCODE_UNDEFINED_COLUMN),
                      relname ?
                      errmsg("column %s.%s does not exist", relname, colname) :
                      errmsg("column \"%s\" does not exist", colname),
-                     errdetail("There is a column named \"%s\" in table \"%s\", but it cannot be referenced from this
partof the query.", 
-                               colname, state->rfirst->eref->aliasname),
-                     rte_visible_if_lateral(pstate, state->rfirst) ?
-                     errhint("To reference that column, you must mark this subquery with LATERAL.") :
-                     (!relname && rte_visible_if_qualified(pstate, state->rfirst)) ?
-                     errhint("To reference that column, you must use a table-qualified name.") : 0,
                      parser_errposition(pstate, location)));
+        /* Handle case where we have a single alternative spelling to offer */
+        ereport(ERROR,
+                (errcode(ERRCODE_UNDEFINED_COLUMN),
+                 relname ?
+                 errmsg("column %s.%s does not exist", relname, colname) :
+                 errmsg("column \"%s\" does not exist", colname),
+                 errhint("Perhaps you meant to reference the column \"%s.%s\".",
+                         state->rfirst->eref->aliasname,
+                         strVal(list_nth(state->rfirst->eref->colnames,
+                                         state->first - 1))),
+                 parser_errposition(pstate, location)));
     }
     else
     {
         /* Handle case where there are two equally useful column hints */
-        char       *closestsecond;
-
-        closestsecond = strVal(list_nth(state->rsecond->eref->colnames,
-                                        state->second - 1));
-
         ereport(ERROR,
                 (errcode(ERRCODE_UNDEFINED_COLUMN),
                  relname ?
                  errmsg("column %s.%s does not exist", relname, colname) :
                  errmsg("column \"%s\" does not exist", colname),
                  errhint("Perhaps you meant to reference the column \"%s.%s\" or the column \"%s.%s\".",
-                         state->rfirst->eref->aliasname, closestfirst,
-                         state->rsecond->eref->aliasname, closestsecond),
+                         state->rfirst->eref->aliasname,
+                         strVal(list_nth(state->rfirst->eref->colnames,
+                                         state->first - 1)),
+                         state->rsecond->eref->aliasname,
+                         strVal(list_nth(state->rsecond->eref->colnames,
+                                         state->second - 1))),
                  parser_errposition(pstate, location)));
     }
 }
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index ebaa289e12..72a3abad9e 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -5069,8 +5069,8 @@ select ctid from
 ERROR:  column "ctid" does not exist
 LINE 1: select ctid from
                ^
-DETAIL:  There is a column named "ctid" in table "t1", but it cannot be referenced from this part of the query.
-HINT:  To reference that column, you must use a table-qualified name.
+DETAIL:  There are columns named "ctid", but they are in tables that cannot be referenced from this part of the query.
+HINT:  Try using a table-qualified name.
 --
 -- Take care to reference the correct RTE
 --
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index acf28ba580..7c7adbc004 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1194,8 +1194,8 @@ do instead insert into rules_foo2 values (f1);
 ERROR:  column "f1" does not exist
 LINE 2: do instead insert into rules_foo2 values (f1);
                                                   ^
-DETAIL:  There is a column named "f1" in table "old", but it cannot be referenced from this part of the query.
-HINT:  To reference that column, you must use a table-qualified name.
+DETAIL:  There are columns named "f1", but they are in tables that cannot be referenced from this part of the query.
+HINT:  Try using a table-qualified name.
 -- this is the correct way:
 create rule rules_foorule as on insert to rules_foo where f1 < 100
 do instead insert into rules_foo2 values (new.f1);

В списке pgsql-bugs по дате отправления:

Предыдущее
От: PG Bug reporting form
Дата:
Сообщение: BUG #17678: Script exit code: 1332
Следующее
От: Tom Lane
Дата:
Сообщение: Re: BUG #17618: unnecessary filter column <> text even after adding index