Give better hints for ambiguous or unreferenceable columns.
authorTom Lane <[email protected]>
Tue, 22 Nov 2022 23:46:31 +0000 (18:46 -0500)
committerTom Lane <[email protected]>
Tue, 22 Nov 2022 23:46:31 +0000 (18:46 -0500)
Examine ParseNamespaceItem flags to detect whether a column name
is unreferenceable for lack of LATERAL, or could be referenced if
a qualified name were used, and give better hints for such cases.
Also, don't phrase the message to imply that there's only one
matching column when there is really more than one.

Many of the regression test output changes are not very interesting,
but just reflect reclassifying the "There is a column ... but it
cannot be referenced from this part of the query" messages as DETAIL
rather than HINT.  They are details per our style guide, in the sense
of being factual rather than offering advice; and this change provides
room to offer actual HINTs about what to do.

While here, adjust the fuzzy-name-matching code to be a shade less
impenetrable.  It was overloading the meanings of FuzzyAttrMatchState
fields way too much IMO, so splitting them into multiple fields seems
to make it clearer.  It's not like we need to shave bytes in that
struct.

Per discussion of bug #17233 from Alexander Korolev.

Discussion: https://postgr.es/m/17233-afb9d806aaa64b17@postgresql.org

src/backend/parser/parse_relation.c
src/test/regress/expected/insert_conflict.out
src/test/regress/expected/join.out
src/test/regress/expected/merge.out
src/test/regress/expected/rules.out
src/test/regress/expected/union.out
src/test/regress/sql/join.sql

index 81f9ae2f024df8cb17fed70b58ae05b46406ea60..4665f0b2b7cb61436256b8afb0903f50d6418c0f 100644 (file)
 /*
  * 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
@@ -81,6 +99,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);
 
 
@@ -610,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;
        }
    }
 }
@@ -923,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,
@@ -949,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)
    {
@@ -960,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
@@ -986,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;
+               }
            }
        }
 
@@ -3603,17 +3619,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),
@@ -3633,68 +3659,155 @@ 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,
     * emit a hint about it.
-    *
-    * TODO: improve this code (and also errorMissingRTE) to mention using
-    * LATERAL if appropriate.
     */
    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)
    {
        /*
-        * Handle case where there is zero or one column suggestions to hint,
-        * including exact matches referenced but not visible.
+        * 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),
+                    errdetail("There are columns named \"%s\", but they are in tables that cannot be referenced from this part of the query.",
+                              colname),
+                    !relname ? errhint("Try using a table-qualified name.") : 0,
+                    parser_errposition(pstate, location)));
+       /* 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 of the 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),
+                    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),
-                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 of the query.",
-                        colname, state->rfirst->eref->aliasname) : 0,
+                        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)));
    }
 }
 
+/*
+ * 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
index 66d8633e3ec569635b7179961597a4658a3ca0b2..9e9e3bd00cd13fd02d9114c2ceb604bb24bbf72a 100644 (file)
@@ -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
index 80638d84a043593826f3892e12e963f41d29aa16..21ca7d04bc1529430356cb4d31ba3e1413a06e21 100644 (file)
@@ -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  
 ---+---+-----+----
@@ -5064,7 +5064,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 
@@ -5092,6 +5092,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 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
 --
@@ -6125,22 +6132,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"
@@ -6170,12 +6181,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"
@@ -6190,12 +6201,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;
index d2e333772720eafecdcabd590e1fddc4756b7387..9d4d0febeec840c0d710d38015e16cee06635b36 100644 (file)
@@ -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;
index 624d0e5aae18b1e34d8af5c0cc1b9f79f37823a0..7c7adbc0045a5585c08d598a88da90682e341a7b 100644 (file)
@@ -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 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);
index dece7310cfef8aed3aff28ca9ae7e121f52bb27c..e2613d6777e38f37a302ecc17e59df3a4cd3659c 100644 (file)
@@ -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        
index a81c7dce7d55ac8587c505f9e2fd6316e93b72d5..50c19d822bf117411559c80a256fa482c47f6867 100644 (file)
@@ -1841,6 +1841,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