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.
This commit is contained in:
Tom Lane 2009-10-31 01:41:31 +00:00
parent 8442317beb
commit fb5d05805b
15 changed files with 944 additions and 520 deletions

View File

@ -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.

View File

@ -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

View File

@ -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)

View File

@ -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);
}

View File

@ -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);

View File

@ -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);

View File

@ -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.

View File

@ -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;

View File

@ -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 <limits.h>
#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);
}

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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 */

View File

@ -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,