diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2009-10-31 01:41:31 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2009-10-31 01:41:31 +0000 |
commit | fb5d05805b3f7658e47ad2c6a4631e497bb3f627 (patch) | |
tree | 74ee6327b45c313ac0ac21e92a3d61a52135cd2f /src/backend/parser/parse_expr.c | |
parent | 8442317beb8fb6f180880a977c03ccae4304df25 (diff) | |
download | postgresql-fb5d05805b3f7658e47ad2c6a4631e497bb3f627.tar.gz postgresql-fb5d05805b3f7658e47ad2c6a4631e497bb3f627.zip |
Implement parser hooks for processing ColumnRef and ParamRef nodes, as per my
recent proposal. As proof of concept, remove knowledge of Params from the
core parser, arranging for them to be handled entirely by parser hook
functions. It turns out we need an additional hook for that --- I had
forgotten about the code that handles inferring a parameter's type from
context.
This is a preliminary step towards letting plpgsql handle its variables
through parser hooks. Additional work remains to be done to expose the
facility through SPI, but I think this is all the changes needed in the core
parser.
Diffstat (limited to 'src/backend/parser/parse_expr.c')
-rw-r--r-- | src/backend/parser/parse_expr.c | 431 |
1 files changed, 265 insertions, 166 deletions
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 2146329ad89..1e76d3b546f 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.246 2009/10/27 17:11:18 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.247 2009/10/31 01:41:31 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -60,8 +60,8 @@ static Node *transformXmlSerialize(ParseState *pstate, XmlSerialize *xs); static Node *transformBooleanTest(ParseState *pstate, BooleanTest *b); static Node *transformCurrentOfExpr(ParseState *pstate, CurrentOfExpr *cexpr); static Node *transformColumnRef(ParseState *pstate, ColumnRef *cref); -static Node *transformWholeRowRef(ParseState *pstate, char *schemaname, - char *relname, int location); +static Node *transformWholeRowRef(ParseState *pstate, RangeTblEntry *rte, + int location); static Node *transformIndirection(ParseState *pstate, Node *basenode, List *indirection); static Node *transformTypeCast(ParseState *pstate, TypeCast *tc); @@ -327,11 +327,64 @@ transformExpr(ParseState *pstate, Node *expr) return result; } +/* + * helper routine for delivering "column does not exist" error message + * + * (Usually we don't have to work this hard, but the general case of field + * selection from an arbitrary node needs it.) + */ +static void +unknown_attribute(ParseState *pstate, Node *relref, char *attname, + int location) +{ + RangeTblEntry *rte; + + if (IsA(relref, Var) && + ((Var *) relref)->varattno == InvalidAttrNumber) + { + /* Reference the RTE by alias not by actual table name */ + rte = GetRTEByRangeTablePosn(pstate, + ((Var *) relref)->varno, + ((Var *) relref)->varlevelsup); + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column %s.%s does not exist", + rte->eref->aliasname, attname), + parser_errposition(pstate, location))); + } + else + { + /* Have to do it by reference to the type of the expression */ + Oid relTypeId = exprType(relref); + + if (ISCOMPLEX(relTypeId)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" not found in data type %s", + attname, format_type_be(relTypeId)), + parser_errposition(pstate, location))); + else if (relTypeId == RECORDOID) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("could not identify column \"%s\" in record data type", + attname), + parser_errposition(pstate, location))); + else + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("column notation .%s applied to type %s, " + "which is not a composite type", + attname, format_type_be(relTypeId)), + parser_errposition(pstate, location))); + } +} + static Node * transformIndirection(ParseState *pstate, Node *basenode, List *indirection) { Node *result = basenode; List *subscripts = NIL; + int location = exprLocation(basenode); ListCell *i; /* @@ -350,10 +403,12 @@ transformIndirection(ParseState *pstate, Node *basenode, List *indirection) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("row expansion via \"*\" is not supported here"), - parser_errposition(pstate, exprLocation(basenode)))); + parser_errposition(pstate, location))); } else { + Node *newresult; + Assert(IsA(n, String)); /* process subscripts before this field selection */ @@ -367,11 +422,14 @@ transformIndirection(ParseState *pstate, Node *basenode, List *indirection) NULL); subscripts = NIL; - result = ParseFuncOrColumn(pstate, - list_make1(n), - list_make1(result), - false, false, false, - NULL, true, -1); + newresult = ParseFuncOrColumn(pstate, + list_make1(n), + list_make1(result), + false, false, false, + NULL, true, location); + if (newresult == NULL) + unknown_attribute(pstate, result, strVal(n), location); + result = newresult; } } /* process trailing subscripts, if any */ @@ -387,12 +445,37 @@ transformIndirection(ParseState *pstate, Node *basenode, List *indirection) return result; } +/* + * Transform a ColumnRef. + * + * If you find yourself changing this code, see also ExpandColumnRefStar. + */ static Node * transformColumnRef(ParseState *pstate, ColumnRef *cref) { - int numnames = list_length(cref->fields); - Node *node; + Node *node = NULL; + char *nspname = NULL; + char *relname = NULL; + char *colname = NULL; + RangeTblEntry *rte; int levels_up; + enum { + CRERR_NO_COLUMN, + CRERR_NO_RTE, + CRERR_WRONG_DB, + CRERR_TOO_MANY + } crerr = CRERR_NO_COLUMN; + + /* + * Give the PreParseColumnRefHook, if any, first shot. If it returns + * non-null then that's all, folks. + */ + if (pstate->p_pre_columnref_hook != NULL) + { + node = (*pstate->p_pre_columnref_hook) (pstate, cref); + if (node != NULL) + return node; + } /*---------- * The allowed syntaxes are: @@ -417,18 +500,17 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) * database name; we check it here and then discard it. *---------- */ - switch (numnames) + switch (list_length(cref->fields)) { case 1: { Node *field1 = (Node *) linitial(cref->fields); - char *name1; Assert(IsA(field1, String)); - name1 = strVal(field1); + colname = strVal(field1); /* Try to identify as an unqualified column */ - node = colNameToVar(pstate, name1, false, cref->location); + node = colNameToVar(pstate, colname, false, cref->location); if (node == NULL) { @@ -441,7 +523,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) * have used VALUE as a column name in the past.) */ if (pstate->p_value_substitute != NULL && - strcmp(name1, "value") == 0) + strcmp(colname, "value") == 0) { node = (Node *) copyObject(pstate->p_value_substitute); @@ -464,17 +546,12 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) * PostQUEL-inspired syntax. The preferred form now is * "rel.*". */ - if (refnameRangeTblEntry(pstate, NULL, name1, - cref->location, - &levels_up) != NULL) - node = transformWholeRowRef(pstate, NULL, name1, + rte = refnameRangeTblEntry(pstate, NULL, colname, + cref->location, + &levels_up); + if (rte) + node = transformWholeRowRef(pstate, rte, cref->location); - else - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_COLUMN), - errmsg("column \"%s\" does not exist", - name1), - parser_errposition(pstate, cref->location))); } break; } @@ -482,36 +559,38 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) { Node *field1 = (Node *) linitial(cref->fields); Node *field2 = (Node *) lsecond(cref->fields); - char *name1; - char *name2; Assert(IsA(field1, String)); - name1 = strVal(field1); + relname = strVal(field1); + + /* Locate the referenced RTE */ + rte = refnameRangeTblEntry(pstate, nspname, relname, + cref->location, + &levels_up); + if (rte == NULL) + { + crerr = CRERR_NO_RTE; + break; + } /* Whole-row reference? */ if (IsA(field2, A_Star)) { - node = transformWholeRowRef(pstate, NULL, name1, - cref->location); + node = transformWholeRowRef(pstate, rte, cref->location); break; } Assert(IsA(field2, String)); - name2 = strVal(field2); + colname = strVal(field2); - /* Try to identify as a once-qualified column */ - node = qualifiedNameToVar(pstate, NULL, name1, name2, - cref->location); + /* Try to identify as a column of the RTE */ + node = scanRTEForColumn(pstate, rte, colname, cref->location); if (node == NULL) { - /* - * Not known as a column of any range-table entry, so try - * it as a function call. - */ - node = transformWholeRowRef(pstate, NULL, name1, - cref->location); + /* Try it as a function call on the whole row */ + node = transformWholeRowRef(pstate, rte, cref->location); node = ParseFuncOrColumn(pstate, - list_make1(makeString(name2)), + list_make1(makeString(colname)), list_make1(node), false, false, false, NULL, true, cref->location); @@ -523,36 +602,40 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) Node *field1 = (Node *) linitial(cref->fields); Node *field2 = (Node *) lsecond(cref->fields); Node *field3 = (Node *) lthird(cref->fields); - char *name1; - char *name2; - char *name3; Assert(IsA(field1, String)); - name1 = strVal(field1); + nspname = strVal(field1); Assert(IsA(field2, String)); - name2 = strVal(field2); + relname = strVal(field2); + + /* Locate the referenced RTE */ + rte = refnameRangeTblEntry(pstate, nspname, relname, + cref->location, + &levels_up); + if (rte == NULL) + { + crerr = CRERR_NO_RTE; + break; + } /* Whole-row reference? */ if (IsA(field3, A_Star)) { - node = transformWholeRowRef(pstate, name1, name2, - cref->location); + node = transformWholeRowRef(pstate, rte, cref->location); break; } Assert(IsA(field3, String)); - name3 = strVal(field3); + colname = strVal(field3); - /* Try to identify as a twice-qualified column */ - node = qualifiedNameToVar(pstate, name1, name2, name3, - cref->location); + /* Try to identify as a column of the RTE */ + node = scanRTEForColumn(pstate, rte, colname, cref->location); if (node == NULL) { - /* Try it as a function call */ - node = transformWholeRowRef(pstate, name1, name2, - cref->location); + /* Try it as a function call on the whole row */ + node = transformWholeRowRef(pstate, rte, cref->location); node = ParseFuncOrColumn(pstate, - list_make1(makeString(name3)), + list_make1(makeString(colname)), list_make1(node), false, false, false, NULL, true, cref->location); @@ -565,49 +648,52 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) Node *field2 = (Node *) lsecond(cref->fields); Node *field3 = (Node *) lthird(cref->fields); Node *field4 = (Node *) lfourth(cref->fields); - char *name1; - char *name2; - char *name3; - char *name4; + char *catname; Assert(IsA(field1, String)); - name1 = strVal(field1); + catname = strVal(field1); Assert(IsA(field2, String)); - name2 = strVal(field2); + nspname = strVal(field2); Assert(IsA(field3, String)); - name3 = strVal(field3); + relname = strVal(field3); /* * We check the catalog name and then ignore it. */ - if (strcmp(name1, get_database_name(MyDatabaseId)) != 0) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cross-database references are not implemented: %s", - NameListToString(cref->fields)), - parser_errposition(pstate, cref->location))); + if (strcmp(catname, get_database_name(MyDatabaseId)) != 0) + { + crerr = CRERR_WRONG_DB; + break; + } + + /* Locate the referenced RTE */ + rte = refnameRangeTblEntry(pstate, nspname, relname, + cref->location, + &levels_up); + if (rte == NULL) + { + crerr = CRERR_NO_RTE; + break; + } /* Whole-row reference? */ if (IsA(field4, A_Star)) { - node = transformWholeRowRef(pstate, name2, name3, - cref->location); + node = transformWholeRowRef(pstate, rte, cref->location); break; } Assert(IsA(field4, String)); - name4 = strVal(field4); + colname = strVal(field4); - /* Try to identify as a twice-qualified column */ - node = qualifiedNameToVar(pstate, name2, name3, name4, - cref->location); + /* Try to identify as a column of the RTE */ + node = scanRTEForColumn(pstate, rte, colname, cref->location); if (node == NULL) { - /* Try it as a function call */ - node = transformWholeRowRef(pstate, name2, name3, - cref->location); + /* Try it as a function call on the whole row */ + node = transformWholeRowRef(pstate, rte, cref->location); node = ParseFuncOrColumn(pstate, - list_make1(makeString(name4)), + list_make1(makeString(colname)), list_make1(node), false, false, false, NULL, true, cref->location); @@ -615,86 +701,101 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) break; } default: - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("improper qualified name (too many dotted names): %s", - NameListToString(cref->fields)), - parser_errposition(pstate, cref->location))); - node = NULL; /* keep compiler quiet */ + crerr = CRERR_TOO_MANY; /* too many dotted names */ break; } - return node; -} - -/* - * Locate the parameter type info for the given parameter number, and - * return a pointer to it. - */ -static Oid * -find_param_type(ParseState *pstate, int paramno, int location) -{ - Oid *result; - /* - * Find topmost ParseState, which is where paramtype info lives. + * Now give the PostParseColumnRefHook, if any, a chance. We pass the + * translation-so-far so that it can throw an error if it wishes in the + * case that it has a conflicting interpretation of the ColumnRef. + * (If it just translates anyway, we'll throw an error, because we can't + * undo whatever effects the preceding steps may have had on the pstate.) + * If it returns NULL, use the standard translation, or throw a suitable + * error if there is none. */ - while (pstate->parentParseState != NULL) - pstate = pstate->parentParseState; - - /* Check parameter number is in range */ - if (paramno <= 0) /* probably can't happen? */ - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_PARAMETER), - errmsg("there is no parameter $%d", paramno), - parser_errposition(pstate, location))); - if (paramno > pstate->p_numparams) + if (pstate->p_post_columnref_hook != NULL) { - if (!pstate->p_variableparams) + Node *hookresult; + + hookresult = (*pstate->p_post_columnref_hook) (pstate, cref, node); + if (node == NULL) + node = hookresult; + else if (hookresult != NULL) ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_PARAMETER), - errmsg("there is no parameter $%d", paramno), - parser_errposition(pstate, location))); - /* Okay to enlarge param array */ - if (pstate->p_paramtypes) - pstate->p_paramtypes = (Oid *) repalloc(pstate->p_paramtypes, - paramno * sizeof(Oid)); - else - pstate->p_paramtypes = (Oid *) palloc(paramno * sizeof(Oid)); - /* Zero out the previously-unreferenced slots */ - MemSet(pstate->p_paramtypes + pstate->p_numparams, - 0, - (paramno - pstate->p_numparams) * sizeof(Oid)); - pstate->p_numparams = paramno; + (errcode(ERRCODE_AMBIGUOUS_COLUMN), + errmsg("column reference \"%s\" is ambiguous", + NameListToString(cref->fields)), + parser_errposition(pstate, cref->location))); } - result = &pstate->p_paramtypes[paramno - 1]; - - if (pstate->p_variableparams) + /* + * Throw error if no translation found. + */ + if (node == NULL) { - /* If not seen before, initialize to UNKNOWN type */ - if (*result == InvalidOid) - *result = UNKNOWNOID; + switch (crerr) + { + case CRERR_NO_COLUMN: + if (relname) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column %s.%s does not exist", + relname, colname), + parser_errposition(pstate, cref->location))); + + else + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" does not exist", + colname), + parser_errposition(pstate, cref->location))); + break; + case CRERR_NO_RTE: + errorMissingRTE(pstate, makeRangeVar(nspname, relname, + cref->location)); + break; + case CRERR_WRONG_DB: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cross-database references are not implemented: %s", + NameListToString(cref->fields)), + parser_errposition(pstate, cref->location))); + break; + case CRERR_TOO_MANY: + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("improper qualified name (too many dotted names): %s", + NameListToString(cref->fields)), + parser_errposition(pstate, cref->location))); + break; + } } - return result; + return node; } static Node * transformParamRef(ParseState *pstate, ParamRef *pref) { - int paramno = pref->number; - Oid *pptype = find_param_type(pstate, paramno, pref->location); - Param *param; - - param = makeNode(Param); - param->paramkind = PARAM_EXTERN; - param->paramid = paramno; - param->paramtype = *pptype; - param->paramtypmod = -1; - param->location = pref->location; - - return (Node *) param; + Node *result; + + /* + * The core parser knows nothing about Params. If a hook is supplied, + * call it. If not, or if the hook returns NULL, throw a generic error. + */ + if (pstate->p_paramref_hook != NULL) + result = (*pstate->p_paramref_hook) (pstate, pref); + else + result = NULL; + + if (result == NULL) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_PARAMETER), + errmsg("there is no parameter $%d", pref->number), + parser_errposition(pstate, pref->location))); + + return result; } /* Test whether an a_expr is a plain NULL constant or not */ @@ -1861,26 +1962,33 @@ transformCurrentOfExpr(ParseState *pstate, CurrentOfExpr *cexpr) &sublevels_up); Assert(sublevels_up == 0); - /* If a parameter is used, it must be of type REFCURSOR */ + /* + * If a parameter is used, it must be of type REFCURSOR. To verify + * that the parameter hooks think so, build a dummy ParamRef and + * transform it. + */ if (cexpr->cursor_name == NULL) { - Oid *pptype = find_param_type(pstate, cexpr->cursor_param, -1); - - if (pstate->p_variableparams && *pptype == UNKNOWNOID) - { - /* resolve unknown param type as REFCURSOR */ - *pptype = REFCURSOROID; - } - else if (*pptype != REFCURSOROID) - { + ParamRef *p = makeNode(ParamRef); + Node *n; + + p->number = cexpr->cursor_param; + p->location = -1; + n = transformParamRef(pstate, p); + /* Allow the parameter type to be inferred if it's unknown */ + if (exprType(n) == UNKNOWNOID) + n = coerce_type(pstate, n, UNKNOWNOID, + REFCURSOROID, -1, + COERCION_IMPLICIT, COERCE_IMPLICIT_CAST, + -1); + if (exprType(n) != REFCURSOROID) ereport(ERROR, (errcode(ERRCODE_AMBIGUOUS_PARAMETER), errmsg("inconsistent types deduced for parameter $%d", cexpr->cursor_param), errdetail("%s versus %s", - format_type_be(*pptype), + format_type_be(exprType(n)), format_type_be(REFCURSOROID)))); - } } return (Node *) cexpr; @@ -1896,23 +2004,14 @@ transformCurrentOfExpr(ParseState *pstate, CurrentOfExpr *cexpr) * a rowtype; either a named composite type, or RECORD. */ static Node * -transformWholeRowRef(ParseState *pstate, char *schemaname, char *relname, - int location) +transformWholeRowRef(ParseState *pstate, RangeTblEntry *rte, int location) { Var *result; - RangeTblEntry *rte; int vnum; int sublevels_up; Oid toid; - /* Look up the referenced RTE, failing if not present */ - - rte = refnameRangeTblEntry(pstate, schemaname, relname, location, - &sublevels_up); - - if (rte == NULL) - errorMissingRTE(pstate, - makeRangeVar(schemaname, relname, location)); + /* Find the RTE's rangetable location */ vnum = RTERangeTablePosn(pstate, rte, &sublevels_up); |