Improve plpgsql's handling of record field references by forcing all potential
authorTom Lane <tgl@sss.pgh.pa.us>
Sun, 10 Jan 2010 17:15:18 +0000 (17:15 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Sun, 10 Jan 2010 17:15:18 +0000 (17:15 +0000)
field references in SQL expressions to have RECFIELD datum-array entries at
parse time.  If it turns out that the reference is actually to a SQL column,
the RECFIELD entry is useless, but it costs little.  This allows us to get rid
of the previous use of FieldSelect applied to a whole-row Param for the record
variable; which was not only slower than a direct RECFIELD reference, but
failed for references to system columns of a trigger's NEW or OLD record.
Per report and fix suggestion from Dean Rasheed.

src/pl/plpgsql/src/gram.y
src/pl/plpgsql/src/pl_comp.c
src/pl/plpgsql/src/pl_scanner.c
src/pl/plpgsql/src/plpgsql.h

index 2e3d504adeef78193cef4154176a23b00b4ef5d7..77afcbec8bb5ed4d82fef20eed3ff1525d9ea4e7 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.137 2010/01/02 16:58:12 momjian Exp $
+ *   $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.138 2010/01/10 17:15:18 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -369,21 +369,21 @@ pl_block      : decl_sect K_BEGIN proc_sect exception_sect K_END opt_label
 decl_sect      : opt_block_label
                    {
                        /* done with decls, so resume identifier lookup */
-                       plpgsql_LookupIdentifiers = true;
+                       plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
                        $$.label      = $1;
                        $$.n_initvars = 0;
                        $$.initvarnos = NULL;
                    }
                | opt_block_label decl_start
                    {
-                       plpgsql_LookupIdentifiers = true;
+                       plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
                        $$.label      = $1;
                        $$.n_initvars = 0;
                        $$.initvarnos = NULL;
                    }
                | opt_block_label decl_start decl_stmts
                    {
-                       plpgsql_LookupIdentifiers = true;
+                       plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
                        if ($3 != NULL)
                            $$.label = $3;
                        else
@@ -401,7 +401,7 @@ decl_start      : K_DECLARE
                         * Disable scanner lookup of identifiers while
                         * we process the decl_stmts
                         */
-                       plpgsql_LookupIdentifiers = false;
+                       plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_DECLARE;
                    }
                ;
 
@@ -2121,7 +2121,7 @@ read_sql_construct(int until,
 {
    int                 tok;
    StringInfoData      ds;
-   bool                save_LookupIdentifiers;
+   IdentifierLookup    save_IdentifierLookup;
    int                 startlocation = -1;
    int                 parenlevel = 0;
    PLpgSQL_expr        *expr;
@@ -2129,9 +2129,9 @@ read_sql_construct(int until,
    initStringInfo(&ds);
    appendStringInfoString(&ds, sqlstart);
 
-   /* no need to lookup identifiers within the SQL text */
-   save_LookupIdentifiers = plpgsql_LookupIdentifiers;
-   plpgsql_LookupIdentifiers = false;
+   /* special lookup mode for identifiers within the SQL text */
+   save_IdentifierLookup = plpgsql_IdentifierLookup;
+   plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_EXPR;
 
    for (;;)
    {
@@ -2176,7 +2176,7 @@ read_sql_construct(int until,
        }
    }
 
-   plpgsql_LookupIdentifiers = save_LookupIdentifiers;
+   plpgsql_IdentifierLookup = save_IdentifierLookup;
 
    if (startloc)
        *startloc = startlocation;
@@ -2221,8 +2221,8 @@ read_datatype(int tok)
    PLpgSQL_type        *result;
    int                 parenlevel = 0;
 
-   /* Should always be called with LookupIdentifiers off */
-   Assert(!plpgsql_LookupIdentifiers);
+   /* Should only be called while parsing DECLARE sections */
+   Assert(plpgsql_IdentifierLookup == IDENTIFIER_LOOKUP_DECLARE);
 
    /* Often there will be a lookahead token, but if not, get one */
    if (tok == YYEMPTY)
@@ -2327,7 +2327,7 @@ static PLpgSQL_stmt *
 make_execsql_stmt(int firsttoken, int location)
 {
    StringInfoData      ds;
-   bool                save_LookupIdentifiers;
+   IdentifierLookup    save_IdentifierLookup;
    PLpgSQL_stmt_execsql *execsql;
    PLpgSQL_expr        *expr;
    PLpgSQL_row         *row = NULL;
@@ -2341,9 +2341,9 @@ make_execsql_stmt(int firsttoken, int location)
 
    initStringInfo(&ds);
 
-   /* no need to lookup identifiers within the SQL text */
-   save_LookupIdentifiers = plpgsql_LookupIdentifiers;
-   plpgsql_LookupIdentifiers = false;
+   /* special lookup mode for identifiers within the SQL text */
+   save_IdentifierLookup = plpgsql_IdentifierLookup;
+   plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_EXPR;
 
    /*
     * We have to special-case the sequence INSERT INTO, because we don't want
@@ -2371,13 +2371,13 @@ make_execsql_stmt(int firsttoken, int location)
                yyerror("INTO specified more than once");
            have_into = true;
            into_start_loc = yylloc;
-           plpgsql_LookupIdentifiers = true;
+           plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
            read_into_target(&rec, &row, &have_strict);
-           plpgsql_LookupIdentifiers = false;
+           plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_EXPR;
        }
    }
 
-   plpgsql_LookupIdentifiers = save_LookupIdentifiers;
+   plpgsql_IdentifierLookup = save_IdentifierLookup;
 
    if (have_into)
    {
index f3adeb63a505029e92ac6da4db7cc0cd78080c14..cc0c3c870cf9d95125b562be87199a1bfa1eeb3e 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.147 2010/01/02 16:58:12 momjian Exp $
+ *   $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.148 2010/01/10 17:15:18 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1132,11 +1132,7 @@ resolve_column_ref(PLpgSQL_expr *expr, ColumnRef *cref)
                return make_datum_param(expr, nse->itemno, cref->location);
            if (nnames == nnames_field)
            {
-               /* colname must be a field in this record */
-               PLpgSQL_rec *rec = (PLpgSQL_rec *) estate->datums[nse->itemno];
-               FieldSelect *fselect;
-               Oid         fldtype;
-               int         fldno;
+               /* colname could be a field in this record */
                int         i;
 
                /* search for a datum referencing this field */
@@ -1153,20 +1149,11 @@ resolve_column_ref(PLpgSQL_expr *expr, ColumnRef *cref)
                }
 
                /*
-                * We can't readily add a recfield datum at runtime, so
-                * instead build a whole-row Param and a FieldSelect node.
-                * This is a bit less efficient, so we prefer the recfield
-                * way when possible.
+                * We should not get here, because a RECFIELD datum should
+                * have been built at parse time for every possible qualified
+                * reference to fields of this record.  But if we do, fall
+                * out and return NULL.
                 */
-               fldtype = exec_get_rec_fieldtype(rec, colname,
-                                                &fldno);
-               fselect = makeNode(FieldSelect);
-               fselect->arg = (Expr *) make_datum_param(expr, nse->itemno,
-                                                        cref->location);
-               fselect->fieldnum = fldno;
-               fselect->resulttype = fldtype;
-               fselect->resulttypmod = -1;
-               return (Node *) fselect;
            }
            break;
        case PLPGSQL_NSTYPE_ROW:
@@ -1174,7 +1161,7 @@ resolve_column_ref(PLpgSQL_expr *expr, ColumnRef *cref)
                return make_datum_param(expr, nse->itemno, cref->location);
            if (nnames == nnames_field)
            {
-               /* colname must be a field in this row */
+               /* colname could be a field in this row */
                PLpgSQL_row *row = (PLpgSQL_row *) estate->datums[nse->itemno];
                int         i;
 
@@ -1187,10 +1174,7 @@ resolve_column_ref(PLpgSQL_expr *expr, ColumnRef *cref)
                                                cref->location);
                    }
                }
-               ereport(ERROR,
-                       (errcode(ERRCODE_UNDEFINED_COLUMN),
-                        errmsg("row \"%s\" has no field \"%s\"",
-                               row->refname, colname)));
+               /* Not found, so return NULL */
            }
            break;
        default:
@@ -1257,8 +1241,12 @@ plpgsql_parse_word(char *word1, const char *yytxt,
 {
    PLpgSQL_nsitem *ns;
 
-   /* No lookup if disabled */
-   if (plpgsql_LookupIdentifiers)
+   /*
+    * We should do nothing in DECLARE sections.  In SQL expressions, there's
+    * no need to do anything either --- lookup will happen when the expression
+    * is compiled.
+    */
+   if (plpgsql_IdentifierLookup == IDENTIFIER_LOOKUP_NORMAL)
    {
        /*
         * Do a lookup in the current namespace stack
@@ -1281,6 +1269,7 @@ plpgsql_parse_word(char *word1, const char *yytxt,
                    return true;
 
                default:
+                   /* plpgsql_ns_lookup should never return anything else */
                    elog(ERROR, "unrecognized plpgsql itemtype: %d",
                         ns->itemtype);
            }
@@ -1313,8 +1302,12 @@ plpgsql_parse_dblword(char *word1, char *word2,
    idents = list_make2(makeString(word1),
                        makeString(word2));
 
-   /* No lookup if disabled */
-   if (plpgsql_LookupIdentifiers)
+   /*
+    * We should do nothing in DECLARE sections.  In SQL expressions,
+    * we really only need to make sure that RECFIELD datums are created
+    * when needed.
+    */
+   if (plpgsql_IdentifierLookup != IDENTIFIER_LOOKUP_DECLARE)
    {
        /*
         * Do a lookup in the current namespace stack
@@ -1338,8 +1331,10 @@ plpgsql_parse_dblword(char *word1, char *word2,
                    if (nnames == 1)
                    {
                        /*
-                        * First word is a record name, so second word must be
-                        * a field in this record.
+                        * First word is a record name, so second word could
+                        * be a field in this record.  We build a RECFIELD
+                        * datum whether it is or not --- any error will be
+                        * detected later.
                         */
                        PLpgSQL_recfield *new;
 
@@ -1366,8 +1361,9 @@ plpgsql_parse_dblword(char *word1, char *word2,
                    if (nnames == 1)
                    {
                        /*
-                        * First word is a row name, so second word must be a
-                        * field in this row.
+                        * First word is a row name, so second word could be
+                        * a field in this row.  Again, no error now if it
+                        * isn't.
                         */
                        PLpgSQL_row *row;
                        int         i;
@@ -1385,10 +1381,7 @@ plpgsql_parse_dblword(char *word1, char *word2,
                                return true;
                            }
                        }
-                       ereport(ERROR,
-                               (errcode(ERRCODE_UNDEFINED_COLUMN),
-                                errmsg("row \"%s\" has no field \"%s\"",
-                                       word1, word2)));
+                       /* fall through to return CWORD */
                    }
                    else
                    {
@@ -1399,6 +1392,7 @@ plpgsql_parse_dblword(char *word1, char *word2,
                        wdatum->idents = idents;
                        return true;
                    }
+                   break;
 
                default:
                    break;
@@ -1429,8 +1423,12 @@ plpgsql_parse_tripword(char *word1, char *word2, char *word3,
                        makeString(word2),
                        makeString(word3));
 
-   /* No lookup if disabled */
-   if (plpgsql_LookupIdentifiers)
+   /*
+    * We should do nothing in DECLARE sections.  In SQL expressions,
+    * we really only need to make sure that RECFIELD datums are created
+    * when needed.
+    */
+   if (plpgsql_IdentifierLookup != IDENTIFIER_LOOKUP_DECLARE)
    {
        /*
         * Do a lookup in the current namespace stack. Must find a qualified
@@ -1446,7 +1444,7 @@ plpgsql_parse_tripword(char *word1, char *word2, char *word3,
                case PLPGSQL_NSTYPE_REC:
                {
                    /*
-                    * words 1/2 are a record name, so third word must be a
+                    * words 1/2 are a record name, so third word could be a
                     * field in this record.
                     */
                    PLpgSQL_recfield *new;
@@ -1468,8 +1466,8 @@ plpgsql_parse_tripword(char *word1, char *word2, char *word3,
                case PLPGSQL_NSTYPE_ROW:
                {
                    /*
-                    * words 1/2 are a row name, so third word must be a field
-                    * in this row.
+                    * words 1/2 are a row name, so third word could be a
+                    * field in this row.
                     */
                    PLpgSQL_row *row;
                    int         i;
@@ -1487,10 +1485,8 @@ plpgsql_parse_tripword(char *word1, char *word2, char *word3,
                            return true;
                        }
                    }
-                   ereport(ERROR,
-                           (errcode(ERRCODE_UNDEFINED_COLUMN),
-                            errmsg("row \"%s.%s\" has no field \"%s\"",
-                                   word1, word2, word3)));
+                   /* fall through to return CWORD */
+                   break;
                }
 
                default:
index 0a5767ef950343eebe53cce3eb8ede7a5f3568df..8e97ffde9154bc9a2b3d69909b949efc01b01579 100644 (file)
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_scanner.c,v 1.3 2010/01/02 16:58:13 momjian Exp $
+ *   $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_scanner.c,v 1.4 2010/01/10 17:15:18 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -23,8 +23,8 @@
 #define PG_KEYWORD(a,b,c) {a,b,c},
 
 
-/* Klugy flag to tell scanner whether to lookup identifiers */
-bool   plpgsql_LookupIdentifiers = true;
+/* Klugy flag to tell scanner how to look up identifiers */
+IdentifierLookup   plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
 
 /*
  * A word about keywords:
@@ -33,11 +33,10 @@ bool    plpgsql_LookupIdentifiers = true;
  * reserved keywords are passed to the core scanner, so they will be
  * recognized before (and instead of) any variable name.  Unreserved
  * words are checked for separately, after determining that the identifier
- * isn't a known variable name.  If plpgsql_LookupIdentifiers is off then
+ * isn't a known variable name.  If plpgsql_IdentifierLookup is DECLARE then
  * no variable names will be recognized, so the unreserved words always work.
  * (Note in particular that this helps us avoid reserving keywords that are
- * only needed in DECLARE sections, since we scan those sections with
- * plpgsql_LookupIdentifiers off.)
+ * only needed in DECLARE sections.)
  *
  * In certain contexts it is desirable to prefer recognizing an unreserved
  * keyword over recognizing a variable name.  Those cases are handled in
@@ -193,7 +192,7 @@ static void location_lineno_init(void);
  * It is a wrapper around the core lexer, with the ability to recognize
  * PL/pgSQL variables and return them as special T_DATUM tokens.  If a
  * word or compound word does not match any variable name, or if matching
- * is turned off by plpgsql_LookupIdentifiers, it is returned as
+ * is turned off by plpgsql_IdentifierLookup, it is returned as
  * T_WORD or T_CWORD respectively, or as an unreserved keyword if it
  * matches one of those.
  */
@@ -567,7 +566,7 @@ plpgsql_scanner_init(const char *str)
    scanorig = str;
 
    /* Other setup */
-   plpgsql_LookupIdentifiers = true;
+   plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
 
    num_pushbacks = 0;
 
index c2d49cb6d9e6cbbfdf8466e540e64ba05186cc4c..4601a4f8136eb4b1a943c2a4df1ed473c10e55b3 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.127 2010/01/02 16:58:13 momjian Exp $
+ *   $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.128 2010/01/10 17:15:18 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -795,11 +795,19 @@ typedef struct
  * Global variable declarations
  **********************************************************************/
 
+typedef enum
+{
+   IDENTIFIER_LOOKUP_NORMAL,       /* normal processing of var names */
+   IDENTIFIER_LOOKUP_DECLARE,      /* In DECLARE --- don't look up names */
+   IDENTIFIER_LOOKUP_EXPR          /* In SQL expression --- special case */
+} IdentifierLookup;
+
+extern IdentifierLookup plpgsql_IdentifierLookup;
+
 extern int plpgsql_variable_conflict;
 
 extern bool plpgsql_check_syntax;
 extern bool plpgsql_DumpExecTree;
-extern bool plpgsql_LookupIdentifiers;
 
 extern PLpgSQL_stmt_block *plpgsql_parse_result;