diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index 2c327d45f15..0c361557a9a 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -13,7 +13,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/namespace.c,v 1.119 2009/10/08 02:39:17 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/namespace.c,v 1.120 2009/10/31 01:41:31 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -2289,6 +2289,38 @@ DeconstructQualifiedName(List *names, *objname_p = objname; } +/* + * LookupNamespaceNoError + * Look up a schema name. + * + * Returns the namespace OID, or InvalidOid if not found. + * + * Note this does NOT perform any permissions check --- callers are + * responsible for being sure that an appropriate check is made. + * In the majority of cases LookupExplicitNamespace is preferable. + */ +Oid +LookupNamespaceNoError(const char *nspname) +{ + /* check for pg_temp alias */ + if (strcmp(nspname, "pg_temp") == 0) + { + if (OidIsValid(myTempNamespace)) + return myTempNamespace; + + /* + * Since this is used only for looking up existing objects, there is + * no point in trying to initialize the temp namespace here; and doing + * so might create problems for some callers. Just report "not found". + */ + return InvalidOid; + } + + return GetSysCacheOid(NAMESPACENAME, + CStringGetDatum(nspname), + 0, 0, 0); +} + /* * LookupExplicitNamespace * Process an explicitly-specified schema name: look up the schema @@ -2336,8 +2368,8 @@ LookupExplicitNamespace(const char *nspname) * LookupCreationNamespace * Look up the schema and verify we have CREATE rights on it. * - * This is just like LookupExplicitNamespace except for the permission check, - * and that we are willing to create pg_temp if needed. + * This is just like LookupExplicitNamespace except for the different + * permission check, and that we are willing to create pg_temp if needed. * * Note: calling this may result in a CommandCounterIncrement operation, * if we have to create or clean out the temp namespace. diff --git a/src/backend/parser/Makefile b/src/backend/parser/Makefile index 1ef3bb24db1..9e58d83dfe4 100644 --- a/src/backend/parser/Makefile +++ b/src/backend/parser/Makefile @@ -2,7 +2,7 @@ # # Makefile for parser # -# $PostgreSQL: pgsql/src/backend/parser/Makefile,v 1.51 2009/08/28 20:26:19 petere Exp $ +# $PostgreSQL: pgsql/src/backend/parser/Makefile,v 1.52 2009/10/31 01:41:31 tgl Exp $ # #------------------------------------------------------------------------- @@ -12,9 +12,10 @@ include $(top_builddir)/src/Makefile.global override CPPFLAGS := -I$(srcdir) $(CPPFLAGS) -OBJS= analyze.o gram.o keywords.o parser.o parse_agg.o parse_cte.o parse_clause.o \ - parse_expr.o parse_func.o parse_node.o parse_oper.o parse_relation.o \ - parse_type.o parse_coerce.o parse_target.o parse_utilcmd.o scansup.o kwlookup.o +OBJS= analyze.o gram.o keywords.o kwlookup.o parser.o \ + parse_agg.o parse_clause.o parse_coerce.o parse_cte.o parse_expr.o \ + parse_func.o parse_node.o parse_oper.o parse_param.o parse_relation.o \ + parse_target.o parse_type.o parse_utilcmd.o scansup.o FLEXFLAGS = -CF diff --git a/src/backend/parser/README b/src/backend/parser/README index 36b6aeb7df7..10a5be652c1 100644 --- a/src/backend/parser/README +++ b/src/backend/parser/README @@ -1,4 +1,4 @@ -$PostgreSQL: pgsql/src/backend/parser/README,v 1.10 2008/04/09 01:00:46 momjian Exp $ +$PostgreSQL: pgsql/src/backend/parser/README,v 1.11 2009/10/31 01:41:31 tgl Exp $ Parser ====== @@ -10,17 +10,20 @@ to the optimizer and then executor. parser.c things start here scan.l break query into tokens scansup.c handle escapes in input strings -keywords.c turn keywords into specific tokens -gram.y parse the tokens and fill query-type-specific structures +kwlookup.c turn keywords into specific tokens +keywords.c table of standard keywords (passed to kwlookup.c) +gram.y parse the tokens and produce a "raw" parse tree analyze.c top level of parse analysis for optimizable queries +parse_agg.c handle aggregates, like SUM(col1), AVG(col2), ... parse_clause.c handle clauses like WHERE, ORDER BY, GROUP BY, ... parse_coerce.c handle coercing expressions to different data types +parse_cte.c handle Common Table Expressions (WITH clauses) parse_expr.c handle expressions like col, col + 3, x = 3 or x = 4 -parse_oper.c handle operators in expressions -parse_agg.c handle aggregates, like SUM(col1), AVG(col2), ... parse_func.c handle functions, table.column and column identifiers parse_node.c create nodes for various structures -parse_target.c handle the result list of the query +parse_oper.c handle operators in expressions +parse_param.c handle Params (for the cases used in the core backend) parse_relation.c support routines for tables and column handling +parse_target.c handle the result list of the query parse_type.c support routines for data type handling parse_utilcmd.c parse analysis for utility commands (done at execution time) diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index c89c7c82a08..49ec8481846 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -17,7 +17,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.395 2009/10/28 14:55:43 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.396 2009/10/31 01:41:31 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -35,6 +35,7 @@ #include "parser/parse_coerce.h" #include "parser/parse_cte.h" #include "parser/parse_oper.h" +#include "parser/parse_param.h" #include "parser/parse_relation.h" #include "parser/parse_target.h" #include "parser/parsetree.h" @@ -62,7 +63,6 @@ static Query *transformExplainStmt(ParseState *pstate, ExplainStmt *stmt); static void transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc, bool pushedDown); -static bool check_parameter_resolution_walker(Node *node, ParseState *pstate); /* @@ -86,9 +86,9 @@ parse_analyze(Node *parseTree, const char *sourceText, Assert(sourceText != NULL); /* required as of 8.4 */ pstate->p_sourcetext = sourceText; - pstate->p_paramtypes = paramTypes; - pstate->p_numparams = numParams; - pstate->p_variableparams = false; + + if (numParams > 0) + parse_fixed_parameters(pstate, paramTypes, numParams); query = transformStmt(pstate, parseTree); @@ -114,18 +114,13 @@ parse_analyze_varparams(Node *parseTree, const char *sourceText, Assert(sourceText != NULL); /* required as of 8.4 */ pstate->p_sourcetext = sourceText; - pstate->p_paramtypes = *paramTypes; - pstate->p_numparams = *numParams; - pstate->p_variableparams = true; + + parse_variable_parameters(pstate, paramTypes, numParams); query = transformStmt(pstate, parseTree); /* make sure all is well with parameter types */ - if (pstate->p_numparams > 0) - check_parameter_resolution_walker((Node *) query, pstate); - - *paramTypes = pstate->p_paramtypes; - *numParams = pstate->p_numparams; + check_variable_parameters(pstate, query); free_parsestate(pstate); @@ -1982,7 +1977,7 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt) * * EXPLAIN is just like other utility statements in that we emit it as a * CMD_UTILITY Query node with no transformation of the raw parse tree. - * However, if p_variableparams is set, it could be that the client is + * However, if p_coerce_param_hook is set, it could be that the client is * expecting us to resolve parameter types in something like * EXPLAIN SELECT * FROM tab WHERE col = $1 * To deal with such cases, we run parse analysis and throw away the result; @@ -1996,7 +1991,7 @@ transformExplainStmt(ParseState *pstate, ExplainStmt *stmt) { Query *result; - if (pstate->p_variableparams) + if (pstate->p_coerce_param_hook != NULL) { /* Since parse analysis scribbles on its input, copy the tree first! */ (void) transformStmt(pstate, copyObject(stmt->query)); @@ -2239,50 +2234,3 @@ applyLockingClause(Query *qry, Index rtindex, rc->pushedDown = pushedDown; qry->rowMarks = lappend(qry->rowMarks, rc); } - - -/* - * Traverse a fully-analyzed tree to verify that parameter symbols - * match their types. We need this because some Params might still - * be UNKNOWN, if there wasn't anything to force their coercion, - * and yet other instances seen later might have gotten coerced. - */ -static bool -check_parameter_resolution_walker(Node *node, ParseState *pstate) -{ - if (node == NULL) - return false; - if (IsA(node, Param)) - { - Param *param = (Param *) node; - - if (param->paramkind == PARAM_EXTERN) - { - int paramno = param->paramid; - - if (paramno <= 0 || /* shouldn't happen, but... */ - paramno > pstate->p_numparams) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_PARAMETER), - errmsg("there is no parameter $%d", paramno), - parser_errposition(pstate, param->location))); - - if (param->paramtype != pstate->p_paramtypes[paramno - 1]) - ereport(ERROR, - (errcode(ERRCODE_AMBIGUOUS_PARAMETER), - errmsg("could not determine data type of parameter $%d", - paramno), - parser_errposition(pstate, param->location))); - } - return false; - } - if (IsA(node, Query)) - { - /* Recurse into RTE subquery or not-yet-planned sublink subquery */ - return query_tree_walker((Query *) node, - check_parameter_resolution_walker, - (void *) pstate, 0); - } - return expression_tree_walker(node, check_parameter_resolution_walker, - (void *) pstate); -} diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c index 0aec4a850e2..7f5f462d175 100644 --- a/src/backend/parser/parse_coerce.c +++ b/src/backend/parser/parse_coerce.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_coerce.c,v 2.177 2009/06/11 14:49:00 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_coerce.c,v 2.178 2009/10/31 01:41:31 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -259,69 +259,21 @@ coerce_type(ParseState *pstate, Node *node, return result; } - if (inputTypeId == UNKNOWNOID && IsA(node, Param) && - ((Param *) node)->paramkind == PARAM_EXTERN && - pstate != NULL && pstate->p_variableparams) + if (IsA(node, Param) && + pstate != NULL && pstate->p_coerce_param_hook != NULL) { /* - * Input is a Param of previously undetermined type, and we want to - * update our knowledge of the Param's type. Find the topmost - * ParseState and update the state. + * Allow the CoerceParamHook to decide what happens. It can return + * a transformed node (very possibly the same Param node), or return + * NULL to indicate we should proceed with normal coercion. */ - Param *param = (Param *) node; - int paramno = param->paramid; - ParseState *toppstate; - - toppstate = pstate; - while (toppstate->parentParseState != NULL) - toppstate = toppstate->parentParseState; - - if (paramno <= 0 || /* shouldn't happen, but... */ - paramno > toppstate->p_numparams) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_PARAMETER), - errmsg("there is no parameter $%d", paramno), - parser_errposition(pstate, param->location))); - - if (toppstate->p_paramtypes[paramno - 1] == UNKNOWNOID) - { - /* We've successfully resolved the type */ - toppstate->p_paramtypes[paramno - 1] = targetTypeId; - } - else if (toppstate->p_paramtypes[paramno - 1] == targetTypeId) - { - /* We previously resolved the type, and it matches */ - } - else - { - /* Ooops */ - ereport(ERROR, - (errcode(ERRCODE_AMBIGUOUS_PARAMETER), - errmsg("inconsistent types deduced for parameter $%d", - paramno), - errdetail("%s versus %s", - format_type_be(toppstate->p_paramtypes[paramno - 1]), - format_type_be(targetTypeId)), - parser_errposition(pstate, param->location))); - } - - param->paramtype = targetTypeId; - - /* - * Note: it is tempting here to set the Param's paramtypmod to - * targetTypeMod, but that is probably unwise because we have no - * infrastructure that enforces that the value delivered for a Param - * will match any particular typmod. Leaving it -1 ensures that a - * run-time length check/coercion will occur if needed. - */ - param->paramtypmod = -1; - - /* Use the leftmost of the param's and coercion's locations */ - if (location >= 0 && - (param->location < 0 || location < param->location)) - param->location = location; - - return (Node *) param; + result = (*pstate->p_coerce_param_hook) (pstate, + (Param *) node, + targetTypeId, + targetTypeMod, + location); + if (result) + return result; } pathtype = find_coercion_pathway(targetTypeId, inputTypeId, ccontext, &funcId); 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; } + /* + * 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. + */ + if (pstate->p_post_columnref_hook != NULL) + { + Node *hookresult; + + hookresult = (*pstate->p_post_columnref_hook) (pstate, cref, node); + if (node == NULL) + node = hookresult; + else if (hookresult != NULL) + ereport(ERROR, + (errcode(ERRCODE_AMBIGUOUS_COLUMN), + errmsg("column reference \"%s\" is ambiguous", + NameListToString(cref->fields)), + parser_errposition(pstate, cref->location))); + } + + /* + * Throw error if no translation found. + */ + if (node == NULL) + { + 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 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. - */ - 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_variableparams) - 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; - } - - result = &pstate->p_paramtypes[paramno - 1]; - - if (pstate->p_variableparams) - { - /* If not seen before, initialize to UNKNOWN type */ - if (*result == InvalidOid) - *result = UNKNOWNOID; - } - - return result; -} - static Node * transformParamRef(ParseState *pstate, ParamRef *pref) { - int paramno = pref->number; - Oid *pptype = find_param_type(pstate, paramno, pref->location); - Param *param; + Node *result; - param = makeNode(Param); - param->paramkind = PARAM_EXTERN; - param->paramid = paramno; - param->paramtype = *pptype; - param->paramtypmod = -1; - param->location = pref->location; + /* + * 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; - return (Node *) param; + 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); + ParamRef *p = makeNode(ParamRef); + Node *n; - if (pstate->p_variableparams && *pptype == UNKNOWNOID) - { - /* resolve unknown param type as REFCURSOR */ - *pptype = REFCURSOROID; - } - else if (*pptype != REFCURSOROID) - { + 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); diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index e752dd8d1ec..d86bd1732f9 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_func.c,v 1.217 2009/10/08 02:39:23 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_func.c,v 1.218 2009/10/31 01:41:31 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -33,8 +33,6 @@ static Oid FuncNameAsType(List *funcname); static Node *ParseComplexProjection(ParseState *pstate, char *funcname, Node *first_arg, int location); -static void unknown_attribute(ParseState *pstate, Node *relref, char *attname, - int location); /* @@ -53,6 +51,8 @@ static void unknown_attribute(ParseState *pstate, Node *relref, char *attname, * not to affect the semantics. When is_column is true, we should have * a single argument (the putative table), unqualified function name * equal to the column name, and no aggregate or variadic decoration. + * Also, when is_column is true, we return NULL on failure rather than + * reporting a no-such-function error. * * The argument expressions (in fargs) must have been transformed already. */ @@ -253,16 +253,11 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs, /* * Oops. Time to die. * - * If we are dealing with the attribute notation rel.function, give an - * error message that is appropriate for that case. + * If we are dealing with the attribute notation rel.function, + * let the caller handle failure. */ if (is_column) - { - Assert(nargs == 1); - Assert(list_length(funcname) == 1); - unknown_attribute(pstate, first_arg, strVal(linitial(funcname)), - location); - } + return NULL; /* * Else generate a detailed complaint for a function @@ -1343,55 +1338,6 @@ ParseComplexProjection(ParseState *pstate, char *funcname, Node *first_arg, return NULL; /* funcname does not match any column */ } -/* - * helper routine for delivering "column does not exist" error message - */ -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))); - } -} - /* * funcname_signature_string * Build a string representing a function name, including arg types. diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c index f775850e049..0dea02c71b5 100644 --- a/src/backend/parser/parse_node.c +++ b/src/backend/parser/parse_node.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_node.c,v 1.105 2009/06/11 14:49:00 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_node.c,v 1.106 2009/10/31 01:41:31 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -53,7 +53,12 @@ make_parsestate(ParseState *parentParseState) if (parentParseState) { pstate->p_sourcetext = parentParseState->p_sourcetext; - pstate->p_variableparams = parentParseState->p_variableparams; + /* all hooks are copied from parent */ + pstate->p_pre_columnref_hook = parentParseState->p_pre_columnref_hook; + pstate->p_post_columnref_hook = parentParseState->p_post_columnref_hook; + pstate->p_paramref_hook = parentParseState->p_paramref_hook; + pstate->p_coerce_param_hook = parentParseState->p_coerce_param_hook; + pstate->p_ref_hook_state = parentParseState->p_ref_hook_state; } return pstate; diff --git a/src/backend/parser/parse_param.c b/src/backend/parser/parse_param.c new file mode 100644 index 00000000000..1c399706558 --- /dev/null +++ b/src/backend/parser/parse_param.c @@ -0,0 +1,307 @@ +/*------------------------------------------------------------------------- + * + * parse_param.c + * handle parameters in parser + * + * This code covers two cases that are used within the core backend: + * * a fixed list of parameters with known types + * * an expandable list of parameters whose types can optionally + * be determined from context + * In both cases, only explicit $n references (ParamRef nodes) are supported. + * + * Note that other approaches to parameters are possible using the parser + * hooks defined in ParseState. + * + * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $PostgreSQL: pgsql/src/backend/parser/parse_param.c,v 2.1 2009/10/31 01:41:31 tgl Exp $ + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include + +#include "catalog/pg_type.h" +#include "nodes/nodeFuncs.h" +#include "parser/parse_param.h" +#include "utils/builtins.h" + + +typedef struct FixedParamState +{ + Oid *paramTypes; /* array of parameter type OIDs */ + int numParams; /* number of array entries */ +} FixedParamState; + +/* + * In the varparams case, the caller-supplied OID array (if any) can be + * re-palloc'd larger at need. A zero array entry means that parameter number + * hasn't been seen, while UNKNOWNOID means the parameter has been used but + * its type is not yet known. + */ +typedef struct VarParamState +{ + Oid **paramTypes; /* array of parameter type OIDs */ + int *numParams; /* number of array entries */ +} VarParamState; + +static Node *fixed_paramref_hook(ParseState *pstate, ParamRef *pref); +static Node *variable_paramref_hook(ParseState *pstate, ParamRef *pref); +static Node *variable_coerce_param_hook(ParseState *pstate, Param *param, + Oid targetTypeId, int32 targetTypeMod, + int location); +static bool check_parameter_resolution_walker(Node *node, ParseState *pstate); + + +/* + * Set up to process a query containing references to fixed parameters. + */ +void +parse_fixed_parameters(ParseState *pstate, + Oid *paramTypes, int numParams) +{ + FixedParamState *parstate = palloc(sizeof(FixedParamState)); + + parstate->paramTypes = paramTypes; + parstate->numParams = numParams; + pstate->p_ref_hook_state = (void *) parstate; + pstate->p_paramref_hook = fixed_paramref_hook; + /* no need to use p_coerce_param_hook */ +} + +/* + * Set up to process a query containing references to variable parameters. + */ +void +parse_variable_parameters(ParseState *pstate, + Oid **paramTypes, int *numParams) +{ + VarParamState *parstate = palloc(sizeof(VarParamState)); + + parstate->paramTypes = paramTypes; + parstate->numParams = numParams; + pstate->p_ref_hook_state = (void *) parstate; + pstate->p_paramref_hook = variable_paramref_hook; + pstate->p_coerce_param_hook = variable_coerce_param_hook; +} + +/* + * Transform a ParamRef using fixed parameter types. + */ +static Node * +fixed_paramref_hook(ParseState *pstate, ParamRef *pref) +{ + FixedParamState *parstate = (FixedParamState *) pstate->p_ref_hook_state; + int paramno = pref->number; + Param *param; + + /* Check parameter number is in range */ + if (paramno <= 0 || paramno > parstate->numParams) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_PARAMETER), + errmsg("there is no parameter $%d", paramno), + parser_errposition(pstate, pref->location))); + + param = makeNode(Param); + param->paramkind = PARAM_EXTERN; + param->paramid = paramno; + param->paramtype = parstate->paramTypes[paramno - 1]; + param->paramtypmod = -1; + param->location = pref->location; + + return (Node *) param; +} + +/* + * Transform a ParamRef using variable parameter types. + * + * The only difference here is we must enlarge the parameter type array + * as needed. + */ +static Node * +variable_paramref_hook(ParseState *pstate, ParamRef *pref) +{ + VarParamState *parstate = (VarParamState *) pstate->p_ref_hook_state; + int paramno = pref->number; + Oid *pptype; + Param *param; + + /* Check parameter number is in range */ + if (paramno <= 0 || paramno > INT_MAX / sizeof(Oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_PARAMETER), + errmsg("there is no parameter $%d", paramno), + parser_errposition(pstate, pref->location))); + if (paramno > *parstate->numParams) + { + /* Need to enlarge param array */ + if (*parstate->paramTypes) + *parstate->paramTypes = (Oid *) repalloc(*parstate->paramTypes, + paramno * sizeof(Oid)); + else + *parstate->paramTypes = (Oid *) palloc(paramno * sizeof(Oid)); + /* Zero out the previously-unreferenced slots */ + MemSet(*parstate->paramTypes + *parstate->numParams, + 0, + (paramno - *parstate->numParams) * sizeof(Oid)); + *parstate->numParams = paramno; + } + + /* Locate param's slot in array */ + pptype = &(*parstate->paramTypes)[paramno - 1]; + + /* If not seen before, initialize to UNKNOWN type */ + if (*pptype == InvalidOid) + *pptype = UNKNOWNOID; + + param = makeNode(Param); + param->paramkind = PARAM_EXTERN; + param->paramid = paramno; + param->paramtype = *pptype; + param->paramtypmod = -1; + param->location = pref->location; + + return (Node *) param; +} + +/* + * Coerce a Param to a query-requested datatype, in the varparams case. + */ +static Node * +variable_coerce_param_hook(ParseState *pstate, Param *param, + Oid targetTypeId, int32 targetTypeMod, + int location) +{ + if (param->paramkind == PARAM_EXTERN && param->paramtype == UNKNOWNOID) + { + /* + * Input is a Param of previously undetermined type, and we want to + * update our knowledge of the Param's type. + */ + VarParamState *parstate = (VarParamState *) pstate->p_ref_hook_state; + Oid *paramTypes = *parstate->paramTypes; + int paramno = param->paramid; + + if (paramno <= 0 || /* shouldn't happen, but... */ + paramno > *parstate->numParams) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_PARAMETER), + errmsg("there is no parameter $%d", paramno), + parser_errposition(pstate, param->location))); + + if (paramTypes[paramno - 1] == UNKNOWNOID) + { + /* We've successfully resolved the type */ + paramTypes[paramno - 1] = targetTypeId; + } + else if (paramTypes[paramno - 1] == targetTypeId) + { + /* We previously resolved the type, and it matches */ + } + else + { + /* Ooops */ + ereport(ERROR, + (errcode(ERRCODE_AMBIGUOUS_PARAMETER), + errmsg("inconsistent types deduced for parameter $%d", + paramno), + errdetail("%s versus %s", + format_type_be(paramTypes[paramno - 1]), + format_type_be(targetTypeId)), + parser_errposition(pstate, param->location))); + } + + param->paramtype = targetTypeId; + + /* + * Note: it is tempting here to set the Param's paramtypmod to + * targetTypeMod, but that is probably unwise because we have no + * infrastructure that enforces that the value delivered for a Param + * will match any particular typmod. Leaving it -1 ensures that a + * run-time length check/coercion will occur if needed. + */ + param->paramtypmod = -1; + + /* Use the leftmost of the param's and coercion's locations */ + if (location >= 0 && + (param->location < 0 || location < param->location)) + param->location = location; + + return (Node *) param; + } + + /* Else signal to proceed with normal coercion */ + return NULL; +} + +/* + * Check for consistent assignment of variable parameters after completion + * of parsing with parse_variable_parameters. + * + * Note: this code intentionally does not check that all parameter positions + * were used, nor that all got non-UNKNOWN types assigned. Caller of parser + * should enforce that if it's important. + */ +void +check_variable_parameters(ParseState *pstate, Query *query) +{ + VarParamState *parstate = (VarParamState *) pstate->p_ref_hook_state; + + /* If numParams is zero then no Params were generated, so no work */ + if (*parstate->numParams > 0) + (void) query_tree_walker(query, + check_parameter_resolution_walker, + (void *) pstate, 0); +} + +/* + * Traverse a fully-analyzed tree to verify that parameter symbols + * match their types. We need this because some Params might still + * be UNKNOWN, if there wasn't anything to force their coercion, + * and yet other instances seen later might have gotten coerced. + */ +static bool +check_parameter_resolution_walker(Node *node, ParseState *pstate) +{ + if (node == NULL) + return false; + if (IsA(node, Param)) + { + Param *param = (Param *) node; + + if (param->paramkind == PARAM_EXTERN) + { + VarParamState *parstate = (VarParamState *) pstate->p_ref_hook_state; + int paramno = param->paramid; + + if (paramno <= 0 || /* shouldn't happen, but... */ + paramno > *parstate->numParams) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_PARAMETER), + errmsg("there is no parameter $%d", paramno), + parser_errposition(pstate, param->location))); + + if (param->paramtype != (*parstate->paramTypes)[paramno - 1]) + ereport(ERROR, + (errcode(ERRCODE_AMBIGUOUS_PARAMETER), + errmsg("could not determine data type of parameter $%d", + paramno), + parser_errposition(pstate, param->location))); + } + return false; + } + if (IsA(node, Query)) + { + /* Recurse into RTE subquery or not-yet-planned sublink subquery */ + return query_tree_walker((Query *) node, + check_parameter_resolution_walker, + (void *) pstate, 0); + } + return expression_tree_walker(node, check_parameter_resolution_walker, + (void *) pstate); +} diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index 9b35577ec3c..184bc7c47a0 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.146 2009/10/27 17:11:18 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.147 2009/10/31 01:41:31 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -86,7 +86,17 @@ refnameRangeTblEntry(ParseState *pstate, { Oid namespaceId; - namespaceId = LookupExplicitNamespace(schemaname); + /* + * We can use LookupNamespaceNoError() here because we are only + * interested in finding existing RTEs. Checking USAGE permission + * on the schema is unnecessary since it would have already been + * checked when the RTE was made. Furthermore, we want to report + * "RTE not found", not "no permissions for schema", if the name + * happens to match a schema name the user hasn't got access to. + */ + namespaceId = LookupNamespaceNoError(schemaname); + if (!OidIsValid(relId)) + return NULL; relId = get_relname_relid(refname, namespaceId); if (!OidIsValid(relId)) return NULL; @@ -555,32 +565,6 @@ colNameToVar(ParseState *pstate, char *colname, bool localonly, return result; } -/* - * qualifiedNameToVar - * Search for a qualified column name: either refname.colname or - * schemaname.relname.colname. - * - * If found, return the appropriate Var node. - * If not found, return NULL. If the name proves ambiguous, raise error. - */ -Node * -qualifiedNameToVar(ParseState *pstate, - char *schemaname, - char *refname, - char *colname, - int location) -{ - RangeTblEntry *rte; - int sublevels_up; - - rte = refnameRangeTblEntry(pstate, schemaname, refname, location, - &sublevels_up); - if (rte == NULL) - return NULL; - - return scanRTEForColumn(pstate, rte, colname, location); -} - /* * markRTEForSelectPriv * Mark the specified column of an RTE as requiring SELECT privilege @@ -2389,7 +2373,8 @@ errorMissingRTE(ParseState *pstate, RangeVar *relation) /* * Check to see if there are any potential matches in the query's - * rangetable. + * rangetable. (Note: cases involving a bad schema name in the + * RangeVar will throw error immediately here. That seems OK.) */ rte = searchRangeTable(pstate, relation); diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 3aff83955b8..ce3f51ca6e5 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_target.c,v 1.173 2009/10/21 20:22:38 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_target.c,v 1.174 2009/10/31 01:41:31 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -48,6 +48,10 @@ static List *ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref, static List *ExpandAllTables(ParseState *pstate, int location); static List *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind, bool targetlist); +static List *ExpandSingleTable(ParseState *pstate, RangeTblEntry *rte, + int location, bool targetlist); +static List *ExpandRowReference(ParseState *pstate, Node *expr, + bool targetlist); static int FigureColnameInternal(Node *node, char **name); @@ -879,90 +883,135 @@ ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref, * Target item is relation.*, expand that table * * (e.g., SELECT emp.*, dname FROM emp, dept) + * + * Note: this code is a lot like transformColumnRef; it's tempting + * to call that instead and then replace the resulting whole-row Var + * with a list of Vars. However, that would leave us with the + * RTE's selectedCols bitmap showing the whole row as needing + * select permission, as well as the individual columns. That would + * be incorrect (since columns added later shouldn't need select + * permissions). We could try to remove the whole-row permission bit + * after the fact, but duplicating code is less messy. */ - char *schemaname; - char *relname; - RangeTblEntry *rte; - int sublevels_up; - int rtindex; + char *nspname = NULL; + char *relname = NULL; + RangeTblEntry *rte = NULL; + int levels_up; + enum { + CRSERR_NO_RTE, + CRSERR_WRONG_DB, + CRSERR_TOO_MANY + } crserr = CRSERR_NO_RTE; + + /* + * Give the PreParseColumnRefHook, if any, first shot. If it returns + * non-null then we should use that expression. + */ + if (pstate->p_pre_columnref_hook != NULL) + { + Node *node; + + node = (*pstate->p_pre_columnref_hook) (pstate, cref); + if (node != NULL) + return ExpandRowReference(pstate, node, targetlist); + } switch (numnames) { case 2: - schemaname = NULL; relname = strVal(linitial(fields)); + rte = refnameRangeTblEntry(pstate, nspname, relname, + cref->location, + &levels_up); break; case 3: - schemaname = strVal(linitial(fields)); + nspname = strVal(linitial(fields)); relname = strVal(lsecond(fields)); + rte = refnameRangeTblEntry(pstate, nspname, relname, + cref->location, + &levels_up); break; case 4: - { - char *name1 = strVal(linitial(fields)); + { + char *catname = strVal(linitial(fields)); - /* - * 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(fields)), - parser_errposition(pstate, cref->location))); - schemaname = strVal(lsecond(fields)); - relname = strVal(lthird(fields)); + /* + * We check the catalog name and then ignore it. + */ + if (strcmp(catname, get_database_name(MyDatabaseId)) != 0) + { + crserr = CRSERR_WRONG_DB; break; } + nspname = strVal(lsecond(fields)); + relname = strVal(lthird(fields)); + rte = refnameRangeTblEntry(pstate, nspname, relname, + cref->location, + &levels_up); + break; + } default: - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("improper qualified name (too many dotted names): %s", - NameListToString(fields)), - parser_errposition(pstate, cref->location))); - schemaname = NULL; /* keep compiler quiet */ - relname = NULL; + crserr = CRSERR_TOO_MANY; break; } - rte = refnameRangeTblEntry(pstate, schemaname, relname, cref->location, - &sublevels_up); - if (rte == NULL) - errorMissingRTE(pstate, - makeRangeVar(schemaname, relname, cref->location)); - - rtindex = RTERangeTablePosn(pstate, rte, &sublevels_up); - - if (targetlist) + /* + * Now give the PostParseColumnRefHook, if any, a chance. + * We cheat a bit by passing the RangeTblEntry, not a Var, + * as the planned translation. (A single Var wouldn't be + * strictly correct anyway. This convention allows hooks + * that really care to know what is happening.) + */ + if (pstate->p_post_columnref_hook != NULL) { - /* expandRelAttrs handles permissions marking */ - return expandRelAttrs(pstate, rte, rtindex, sublevels_up, - cref->location); - } - else - { - List *vars; - ListCell *l; + Node *node; - expandRTE(rte, rtindex, sublevels_up, cref->location, false, - NULL, &vars); - - /* - * Require read access to the table. This is normally redundant - * with the markVarForSelectPriv calls below, but not if the table - * has zero columns. - */ - rte->requiredPerms |= ACL_SELECT; - - /* Require read access to each column */ - foreach(l, vars) + node = (*pstate->p_post_columnref_hook) (pstate, cref, + (Node *) rte); + if (node != NULL) { - Var *var = (Var *) lfirst(l); - - markVarForSelectPriv(pstate, var, rte); + if (rte != NULL) + ereport(ERROR, + (errcode(ERRCODE_AMBIGUOUS_COLUMN), + errmsg("column reference \"%s\" is ambiguous", + NameListToString(cref->fields)), + parser_errposition(pstate, cref->location))); + return ExpandRowReference(pstate, node, targetlist); } - - return vars; } + + /* + * Throw error if no translation found. + */ + if (rte == NULL) + { + switch (crserr) + { + case CRSERR_NO_RTE: + errorMissingRTE(pstate, makeRangeVar(nspname, relname, + cref->location)); + break; + case CRSERR_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 CRSERR_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; + } + } + + /* + * OK, expand the RTE into fields. + */ + return ExpandSingleTable(pstate, rte, cref->location, targetlist); } } @@ -1015,11 +1064,7 @@ static List * ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind, bool targetlist) { - List *result = NIL; Node *expr; - TupleDesc tupleDesc; - int numAttrs; - int i; /* Strip off the '*' to create a reference to the rowtype object */ ind = copyObject(ind); @@ -1029,7 +1074,102 @@ ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind, /* And transform that */ expr = transformExpr(pstate, (Node *) ind); + /* Expand the rowtype expression into individual fields */ + return ExpandRowReference(pstate, expr, targetlist); +} + +/* + * ExpandSingleTable() + * Transforms foo.* into a list of expressions or targetlist entries. + * + * This handles the case where foo has been determined to be a simple + * reference to an RTE, so we can just generate Vars for the expressions. + * + * The referenced columns are marked as requiring SELECT access. + */ +static List * +ExpandSingleTable(ParseState *pstate, RangeTblEntry *rte, + int location, bool targetlist) +{ + int sublevels_up; + int rtindex; + + rtindex = RTERangeTablePosn(pstate, rte, &sublevels_up); + + if (targetlist) + { + /* expandRelAttrs handles permissions marking */ + return expandRelAttrs(pstate, rte, rtindex, sublevels_up, + location); + } + else + { + List *vars; + ListCell *l; + + expandRTE(rte, rtindex, sublevels_up, location, false, + NULL, &vars); + + /* + * Require read access to the table. This is normally redundant + * with the markVarForSelectPriv calls below, but not if the table + * has zero columns. + */ + rte->requiredPerms |= ACL_SELECT; + + /* Require read access to each column */ + foreach(l, vars) + { + Var *var = (Var *) lfirst(l); + + markVarForSelectPriv(pstate, var, rte); + } + + return vars; + } +} + +/* + * ExpandRowReference() + * Transforms foo.* into a list of expressions or targetlist entries. + * + * This handles the case where foo is an arbitrary expression of composite + * type. + */ +static List * +ExpandRowReference(ParseState *pstate, Node *expr, + bool targetlist) +{ + List *result = NIL; + TupleDesc tupleDesc; + int numAttrs; + int i; + /* + * If the rowtype expression is a whole-row Var, we can expand the fields + * as simple Vars. Note: if the RTE is a relation, this case leaves us + * with the RTE's selectedCols bitmap showing the whole row as needing + * select permission, as well as the individual columns. However, we can + * only get here for weird notations like (table.*).*, so it's not worth + * trying to clean up --- arguably, the permissions marking is correct + * anyway for such cases. + */ + if (IsA(expr, Var) && + ((Var *) expr)->varattno == InvalidAttrNumber) + { + Var *var = (Var *) expr; + RangeTblEntry *rte; + + rte = GetRTEByRangeTablePosn(pstate, var->varno, var->varlevelsup); + return ExpandSingleTable(pstate, rte, var->location, targetlist); + } + + /* + * Otherwise we have to do it the hard way. Our current implementation + * is to generate multiple copies of the expression and do FieldSelects. + * (This can be pretty inefficient if the expression involves nontrivial + * computation :-(.) + * * Verify it's a composite type, and get the tupdesc. We use * get_expr_result_type() because that can handle references to functions * returning anonymous record types. If that fails, use @@ -1053,56 +1193,30 @@ ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind, for (i = 0; i < numAttrs; i++) { Form_pg_attribute att = tupleDesc->attrs[i]; - Node *fieldnode; + FieldSelect *fselect; if (att->attisdropped) continue; - /* - * If we got a whole-row Var from the rowtype reference, we can expand - * the fields as simple Vars. Otherwise we must generate multiple - * copies of the rowtype reference and do FieldSelects. - */ - if (IsA(expr, Var) && - ((Var *) expr)->varattno == InvalidAttrNumber) - { - Var *var = (Var *) expr; - Var *newvar; - - newvar = makeVar(var->varno, - i + 1, - att->atttypid, - att->atttypmod, - var->varlevelsup); - newvar->location = var->location; - - fieldnode = (Node *) newvar; - } - else - { - FieldSelect *fselect = makeNode(FieldSelect); - - fselect->arg = (Expr *) copyObject(expr); - fselect->fieldnum = i + 1; - fselect->resulttype = att->atttypid; - fselect->resulttypmod = att->atttypmod; - - fieldnode = (Node *) fselect; - } + fselect = makeNode(FieldSelect); + fselect->arg = (Expr *) copyObject(expr); + fselect->fieldnum = i + 1; + fselect->resulttype = att->atttypid; + fselect->resulttypmod = att->atttypmod; if (targetlist) { /* add TargetEntry decoration */ TargetEntry *te; - te = makeTargetEntry((Expr *) fieldnode, + te = makeTargetEntry((Expr *) fselect, (AttrNumber) pstate->p_next_resno++, pstrdup(NameStr(att->attname)), false); result = lappend(result, te); } else - result = lappend(result, fieldnode); + result = lappend(result, fselect); } return result; diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h index 2c2b88951a3..55eed83345f 100644 --- a/src/include/catalog/namespace.h +++ b/src/include/catalog/namespace.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/namespace.h,v 1.60 2009/10/08 02:39:23 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/namespace.h,v 1.61 2009/10/31 01:41:31 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -89,6 +89,7 @@ extern bool TSConfigIsVisible(Oid cfgid); extern void DeconstructQualifiedName(List *names, char **nspname_p, char **objname_p); +extern Oid LookupNamespaceNoError(const char *nspname); extern Oid LookupExplicitNamespace(const char *nspname); extern Oid LookupCreationNamespace(const char *nspname); diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h index 5eb446f1204..27aa7f2c612 100644 --- a/src/include/parser/parse_node.h +++ b/src/include/parser/parse_node.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/parser/parse_node.h,v 1.65 2009/10/27 17:11:18 tgl Exp $ + * $PostgreSQL: pgsql/src/include/parser/parse_node.h,v 1.66 2009/10/31 01:41:31 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -17,6 +17,20 @@ #include "nodes/parsenodes.h" #include "utils/relcache.h" + +/* + * Function signatures for parser hooks + */ +typedef struct ParseState ParseState; + +typedef Node * (*PreParseColumnRefHook) (ParseState *pstate, ColumnRef *cref); +typedef Node * (*PostParseColumnRefHook) (ParseState *pstate, ColumnRef *cref, Node *var); +typedef Node * (*ParseParamRefHook) (ParseState *pstate, ParamRef *pref); +typedef Node * (*CoerceParamHook) (ParseState *pstate, Param *param, + Oid targetTypeId, int32 targetTypeMod, + int location); + + /* * State information used during parse analysis * @@ -68,17 +82,8 @@ * afterwards (so that any resjunk tlist items needed for the sort/group * clauses end up at the end of the query tlist). A WindowDef's location in * this list, counting from 1, is the winref number to use to reference it. - * - * p_paramtypes: an array of p_numparams type OIDs for $n parameter symbols - * (zeroth entry in array corresponds to $1). If p_variableparams is true, the - * set of param types is not predetermined; in that case, a zero array entry - * means that parameter number hasn't been seen, and UNKNOWNOID means the - * parameter has been used but its type is not yet known. NOTE: in a stack - * of ParseStates, only the topmost ParseState contains paramtype info; but - * we copy the p_variableparams flag down to the child nodes for speed in - * coerce_type. */ -typedef struct ParseState +struct ParseState { struct ParseState *parentParseState; /* stack link */ const char *p_sourcetext; /* source text, or NULL if not available */ @@ -92,12 +97,9 @@ typedef struct ParseState List *p_future_ctes; /* common table exprs not yet in namespace */ CommonTableExpr *p_parent_cte; /* this query's containing CTE */ List *p_windowdefs; /* raw representations of window clauses */ - Oid *p_paramtypes; /* OIDs of types for $n parameter symbols */ - int p_numparams; /* allocated size of p_paramtypes[] */ int p_next_resno; /* next targetlist resno to assign */ List *p_locking_clause; /* raw FOR UPDATE/FOR SHARE info */ Node *p_value_substitute; /* what to replace VALUE with, if any */ - bool p_variableparams; bool p_hasAggs; bool p_hasWindowFuncs; bool p_hasSubLinks; @@ -106,7 +108,17 @@ typedef struct ParseState bool p_locked_from_parent; Relation p_target_relation; RangeTblEntry *p_target_rangetblentry; -} ParseState; + + /* + * Optional hook functions for parser callbacks. These are null unless + * set up by the caller of make_parsestate. + */ + PreParseColumnRefHook p_pre_columnref_hook; + PostParseColumnRefHook p_post_columnref_hook; + ParseParamRefHook p_paramref_hook; + CoerceParamHook p_coerce_param_hook; + void *p_ref_hook_state; /* common passthrough link for above */ +}; /* Support for parser_errposition_callback function */ typedef struct ParseCallbackState diff --git a/src/include/parser/parse_param.h b/src/include/parser/parse_param.h new file mode 100644 index 00000000000..1423cde97c6 --- /dev/null +++ b/src/include/parser/parse_param.h @@ -0,0 +1,24 @@ +/*------------------------------------------------------------------------- + * + * parse_param.h + * handle parameters in parser + * + * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * $PostgreSQL: pgsql/src/include/parser/parse_param.h,v 1.1 2009/10/31 01:41:31 tgl Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef PARSE_PARAM_H +#define PARSE_PARAM_H + +#include "parser/parse_node.h" + +extern void parse_fixed_parameters(ParseState *pstate, + Oid *paramTypes, int numParams); +extern void parse_variable_parameters(ParseState *pstate, + Oid **paramTypes, int *numParams); +extern void check_variable_parameters(ParseState *pstate, Query *query); + +#endif /* PARSE_PARAM_H */ diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h index 96b60de480e..006dfa75f06 100644 --- a/src/include/parser/parse_relation.h +++ b/src/include/parser/parse_relation.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/parser/parse_relation.h,v 1.66 2009/10/27 17:11:18 tgl Exp $ + * $PostgreSQL: pgsql/src/include/parser/parse_relation.h,v 1.67 2009/10/31 01:41:31 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -38,11 +38,6 @@ extern Node *scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, char *colname, int location); extern Node *colNameToVar(ParseState *pstate, char *colname, bool localonly, int location); -extern Node *qualifiedNameToVar(ParseState *pstate, - char *schemaname, - char *refname, - char *colname, - int location); extern void markVarForSelectPriv(ParseState *pstate, Var *var, RangeTblEntry *rte); extern Relation parserOpenTable(ParseState *pstate, const RangeVar *relation,