From 9caafda579f699b43fa4c89bf13a2331ef00611e Mon Sep 17 00:00:00 2001 From: Joe Conway Date: Wed, 2 Aug 2006 01:59:48 +0000 Subject: [PATCH] Add support for multi-row VALUES clauses as part of INSERT statements (e.g. "INSERT ... VALUES (...), (...), ...") and elsewhere as allowed by the spec. (e.g. similar to a FROM clause subselect). initdb required. Joe Conway and Tom Lane. --- src/backend/catalog/heap.c | 4 +- src/backend/commands/explain.c | 22 +- src/backend/executor/Makefile | 5 +- src/backend/executor/execAmi.c | 22 +- src/backend/executor/execProcnode.c | 19 +- src/backend/executor/nodeValuesscan.c | 332 ++++++++++++++++ src/backend/nodes/copyfuncs.c | 24 +- src/backend/nodes/equalfuncs.c | 5 +- src/backend/nodes/outfuncs.c | 17 +- src/backend/nodes/print.c | 15 +- src/backend/nodes/readfuncs.c | 5 +- src/backend/optimizer/path/allpaths.c | 26 +- src/backend/optimizer/path/costsize.c | 63 +++- src/backend/optimizer/plan/createplan.c | 69 +++- src/backend/optimizer/plan/planner.c | 31 +- src/backend/optimizer/plan/setrefs.c | 20 +- src/backend/optimizer/plan/subselect.c | 13 +- src/backend/optimizer/util/clauses.c | 9 +- src/backend/optimizer/util/pathnode.c | 21 +- src/backend/optimizer/util/plancat.c | 23 +- src/backend/optimizer/util/relnode.c | 11 +- src/backend/parser/analyze.c | 480 ++++++++++++++++++++---- src/backend/parser/gram.y | 109 +++--- src/backend/parser/parse_expr.c | 60 +-- src/backend/parser/parse_node.c | 4 +- src/backend/parser/parse_relation.c | 123 +++++- src/backend/parser/parse_target.c | 159 ++++++-- src/backend/parser/parse_type.c | 3 +- src/backend/rewrite/rewriteHandler.c | 170 ++++++++- src/backend/utils/adt/ruleutils.c | 183 +++++++-- src/include/catalog/catversion.h | 4 +- src/include/executor/nodeValuesscan.h | 27 ++ src/include/nodes/execnodes.h | 23 +- src/include/nodes/nodes.h | 4 +- src/include/nodes/parsenodes.h | 47 ++- src/include/nodes/plannodes.h | 12 +- src/include/optimizer/cost.h | 5 +- src/include/optimizer/pathnode.h | 3 +- src/include/parser/parse_relation.h | 6 +- src/include/parser/parse_target.h | 12 +- 40 files changed, 1877 insertions(+), 313 deletions(-) create mode 100644 src/backend/executor/nodeValuesscan.c create mode 100644 src/include/executor/nodeValuesscan.h diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 922a0cebf7..e1f3e194b6 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.311 2006/07/31 20:09:00 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.312 2006/08/02 01:59:44 joe Exp $ * * * INTERFACE ROUTINES @@ -1841,7 +1841,7 @@ cookDefault(ParseState *pstate, /* * Coerce the expression to the correct type and typmod, if given. This * should match the parser's processing of non-defaulted expressions --- - * see updateTargetListEntry(). + * see transformAssignedExpr(). */ if (OidIsValid(atttypid)) { diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 7c50784aba..5787aa413e 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994-5, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.149 2006/07/14 14:52:18 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.150 2006/08/02 01:59:45 joe Exp $ * *------------------------------------------------------------------------- */ @@ -527,6 +527,9 @@ explain_outNode(StringInfo str, case T_FunctionScan: pname = "Function Scan"; break; + case T_ValuesScan: + pname = "Values Scan"; + break; case T_Material: pname = "Materialize"; break; @@ -666,6 +669,22 @@ explain_outNode(StringInfo str, quote_identifier(rte->eref->aliasname)); } break; + case T_ValuesScan: + if (((Scan *) plan)->scanrelid > 0) + { + RangeTblEntry *rte = rt_fetch(((Scan *) plan)->scanrelid, + es->rtable); + char *valsname; + + /* Assert it's on a values rte */ + Assert(rte->rtekind == RTE_VALUES); + + valsname = rte->eref->aliasname; + + appendStringInfo(str, " on %s", + quote_identifier(valsname)); + } + break; default: break; } @@ -728,6 +747,7 @@ explain_outNode(StringInfo str, case T_SeqScan: case T_SubqueryScan: case T_FunctionScan: + case T_ValuesScan: show_scan_qual(plan->qual, "Filter", ((Scan *) plan)->scanrelid, diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile index e9caf7d837..7bc8eac7f5 100644 --- a/src/backend/executor/Makefile +++ b/src/backend/executor/Makefile @@ -4,7 +4,7 @@ # Makefile for executor # # IDENTIFICATION -# $PostgreSQL: pgsql/src/backend/executor/Makefile,v 1.23 2005/04/19 22:35:11 tgl Exp $ +# $PostgreSQL: pgsql/src/backend/executor/Makefile,v 1.24 2006/08/02 01:59:45 joe Exp $ # #------------------------------------------------------------------------- @@ -19,7 +19,8 @@ OBJS = execAmi.o execGrouping.o execJunk.o execMain.o \ nodeBitmapHeapscan.o nodeBitmapIndexscan.o nodeHash.o \ nodeHashjoin.o nodeIndexscan.o nodeMaterial.o nodeMergejoin.o \ nodeNestloop.o nodeFunctionscan.o nodeResult.o nodeSeqscan.o \ - nodeSetOp.o nodeSort.o nodeUnique.o nodeLimit.o nodeGroup.o \ + nodeSetOp.o nodeSort.o nodeUnique.o \ + nodeValuesscan.o nodeLimit.o nodeGroup.o \ nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o tstoreReceiver.o spi.o all: SUBSYS.o diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c index df692d9bdf..1d5246f9ec 100644 --- a/src/backend/executor/execAmi.c +++ b/src/backend/executor/execAmi.c @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/executor/execAmi.c,v 1.88 2006/07/14 14:52:18 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execAmi.c,v 1.89 2006/08/02 01:59:45 joe Exp $ * *------------------------------------------------------------------------- */ @@ -38,6 +38,7 @@ #include "executor/nodeSubqueryscan.h" #include "executor/nodeTidscan.h" #include "executor/nodeUnique.h" +#include "executor/nodeValuesscan.h" /* @@ -144,6 +145,10 @@ ExecReScan(PlanState *node, ExprContext *exprCtxt) ExecFunctionReScan((FunctionScanState *) node, exprCtxt); break; + case T_ValuesScanState: + ExecValuesReScan((ValuesScanState *) node, exprCtxt); + break; + case T_NestLoopState: ExecReScanNestLoop((NestLoopState *) node, exprCtxt); break; @@ -226,6 +231,10 @@ ExecMarkPos(PlanState *node) ExecFunctionMarkPos((FunctionScanState *) node); break; + case T_ValuesScanState: + ExecValuesMarkPos((ValuesScanState *) node); + break; + case T_MaterialState: ExecMaterialMarkPos((MaterialState *) node); break; @@ -275,6 +284,10 @@ ExecRestrPos(PlanState *node) ExecFunctionRestrPos((FunctionScanState *) node); break; + case T_ValuesScanState: + ExecValuesRestrPos((ValuesScanState *) node); + break; + case T_MaterialState: ExecMaterialRestrPos((MaterialState *) node); break; @@ -298,8 +311,8 @@ ExecRestrPos(PlanState *node) * * (However, since the only present use of mark/restore is in mergejoin, * there is no need to support mark/restore in any plan type that is not - * capable of generating ordered output. So the seqscan, tidscan, and - * functionscan support is actually useless code at present.) + * capable of generating ordered output. So the seqscan, tidscan, + * functionscan, and valuesscan support is actually useless code at present.) */ bool ExecSupportsMarkRestore(NodeTag plantype) @@ -310,6 +323,7 @@ ExecSupportsMarkRestore(NodeTag plantype) case T_IndexScan: case T_TidScan: case T_FunctionScan: + case T_ValuesScan: case T_Material: case T_Sort: return true; @@ -359,6 +373,7 @@ ExecSupportsBackwardScan(Plan *node) case T_IndexScan: case T_TidScan: case T_FunctionScan: + case T_ValuesScan: return true; case T_SubqueryScan: @@ -413,6 +428,7 @@ ExecMayReturnRawTuples(PlanState *node) case T_TidScanState: case T_SubqueryScanState: case T_FunctionScanState: + case T_ValuesScanState: if (node->ps_ProjInfo == NULL) return true; break; diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c index 77f9f2e42e..9f1fd54a8e 100644 --- a/src/backend/executor/execProcnode.c +++ b/src/backend/executor/execProcnode.c @@ -12,7 +12,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execProcnode.c,v 1.57 2006/07/14 14:52:18 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execProcnode.c,v 1.58 2006/08/02 01:59:45 joe Exp $ * *------------------------------------------------------------------------- */ @@ -102,6 +102,7 @@ #include "executor/nodeSubqueryscan.h" #include "executor/nodeTidscan.h" #include "executor/nodeUnique.h" +#include "executor/nodeValuesscan.h" #include "miscadmin.h" /* ------------------------------------------------------------------------ @@ -194,6 +195,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags) estate, eflags); break; + case T_ValuesScan: + result = (PlanState *) ExecInitValuesScan((ValuesScan *) node, + estate, eflags); + break; + /* * join nodes */ @@ -365,6 +371,10 @@ ExecProcNode(PlanState *node) result = ExecFunctionScan((FunctionScanState *) node); break; + case T_ValuesScanState: + result = ExecValuesScan((ValuesScanState *) node); + break; + /* * join nodes */ @@ -536,6 +546,9 @@ ExecCountSlotsNode(Plan *node) case T_FunctionScan: return ExecCountSlotsFunctionScan((FunctionScan *) node); + case T_ValuesScan: + return ExecCountSlotsValuesScan((ValuesScan *) node); + /* * join nodes */ @@ -669,6 +682,10 @@ ExecEndNode(PlanState *node) ExecEndFunctionScan((FunctionScanState *) node); break; + case T_ValuesScanState: + ExecEndValuesScan((ValuesScanState *) node); + break; + /* * join nodes */ diff --git a/src/backend/executor/nodeValuesscan.c b/src/backend/executor/nodeValuesscan.c new file mode 100644 index 0000000000..eb053d8cc7 --- /dev/null +++ b/src/backend/executor/nodeValuesscan.c @@ -0,0 +1,332 @@ +/*------------------------------------------------------------------------- + * + * nodeValuesscan.c + * Support routines for scanning Values lists + * ("VALUES (...), (...), ..." in rangetable). + * + * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * $PostgreSQL: pgsql/src/backend/executor/nodeValuesscan.c,v 1.1 2006/08/02 01:59:45 joe Exp $ + * + *------------------------------------------------------------------------- + */ +/* + * INTERFACE ROUTINES + * ExecValuesScan scans a values list. + * ExecValuesNext retrieve next tuple in sequential order. + * ExecInitValuesScan creates and initializes a valuesscan node. + * ExecEndValuesScan releases any storage allocated. + * ExecValuesReScan rescans the values list + */ +#include "postgres.h" + +#include "executor/executor.h" +#include "executor/nodeValuesscan.h" +#include "parser/parsetree.h" +#include "utils/memutils.h" + + +static TupleTableSlot *ValuesNext(ValuesScanState *node); +static void ExecMakeValuesResult(List *targetlist, + ExprContext *econtext, + TupleTableSlot *slot); + + +/* ---------------------------------------------------------------- + * Scan Support + * ---------------------------------------------------------------- + */ + +/* ---------------------------------------------------------------- + * ValuesNext + * + * This is a workhorse for ExecValuesScan + * ---------------------------------------------------------------- + */ +static TupleTableSlot * +ValuesNext(ValuesScanState *node) +{ + TupleTableSlot *slot; + EState *estate; + ExprContext *econtext; + ScanDirection direction; + List *exprlist; + + /* + * get information from the estate and scan state + */ + estate = node->ss.ps.state; + direction = estate->es_direction; + slot = node->ss.ss_ScanTupleSlot; + econtext = node->ss.ps.ps_ExprContext; + + /* + * Get the next tuple. Return NULL if no more tuples. + */ + if (ScanDirectionIsForward(direction)) + { + if (node->curr_idx < node->array_len) + node->curr_idx++; + if (node->curr_idx < node->array_len) + exprlist = node->exprlists[node->curr_idx]; + else + exprlist = NIL; + } + else + { + if (node->curr_idx >= 0) + node->curr_idx--; + if (node->curr_idx >= 0) + exprlist = node->exprlists[node->curr_idx]; + else + exprlist = NIL; + } + + if (exprlist) + { + List *init_exprlist; + + init_exprlist = (List *) ExecInitExpr((Expr *) exprlist, + (PlanState *) node); + ExecMakeValuesResult(init_exprlist, + econtext, + slot); + list_free_deep(init_exprlist); + } + else + ExecClearTuple(slot); + + return slot; +} + +/* + * ExecMakeValuesResult + * + * Evaluate a values list, store into a virtual slot. + */ +static void +ExecMakeValuesResult(List *targetlist, + ExprContext *econtext, + TupleTableSlot *slot) +{ + MemoryContext oldContext; + Datum *values; + bool *isnull; + ListCell *lc; + int resind = 0; + + /* caller should have checked all targetlists are the same length */ + Assert(list_length(targetlist) == slot->tts_tupleDescriptor->natts); + + /* + * Prepare to build a virtual result tuple. + */ + ExecClearTuple(slot); + values = slot->tts_values; + isnull = slot->tts_isnull; + + /* + * Switch to short-lived context for evaluating the row. + * Reset per-tuple memory context before each row. + */ + ResetExprContext(econtext); + oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); + + foreach(lc, targetlist) + { + ExprState *estate = (ExprState *) lfirst(lc); + + values[resind] = ExecEvalExpr(estate, + econtext, + &isnull[resind], + NULL); + resind++; + } + + MemoryContextSwitchTo(oldContext); + + /* + * And return the virtual tuple. + */ + ExecStoreVirtualTuple(slot); +} + + +/* ---------------------------------------------------------------- + * ExecValuesScan(node) + * + * Scans the values lists sequentially and returns the next qualifying + * tuple. + * It calls the ExecScan() routine and passes it the access method + * which retrieves tuples sequentially. + * ---------------------------------------------------------------- + */ +TupleTableSlot * +ExecValuesScan(ValuesScanState *node) +{ + /* + * use ValuesNext as access method + */ + return ExecScan(&node->ss, (ExecScanAccessMtd) ValuesNext); +} + +/* ---------------------------------------------------------------- + * ExecInitValuesScan + * ---------------------------------------------------------------- + */ +ValuesScanState * +ExecInitValuesScan(ValuesScan *node, EState *estate, int eflags) +{ + ValuesScanState *scanstate; + RangeTblEntry *rte; + TupleDesc tupdesc; + ListCell *vtl; + int i; + PlanState *planstate; + ExprContext *econtext; + + /* + * ValuesScan should not have any children. + */ + Assert(outerPlan(node) == NULL); + Assert(innerPlan(node) == NULL); + + /* + * create new ScanState for node + */ + scanstate = makeNode(ValuesScanState); + scanstate->ss.ps.plan = (Plan *) node; + scanstate->ss.ps.state = estate; + + /* + * Miscellaneous initialization + * + * create expression context for node + */ + planstate = &scanstate->ss.ps; + ExecAssignExprContext(estate, planstate); + econtext = planstate->ps_ExprContext; + +#define VALUESSCAN_NSLOTS 2 + + /* + * tuple table initialization + */ + ExecInitResultTupleSlot(estate, &scanstate->ss.ps); + ExecInitScanTupleSlot(estate, &scanstate->ss); + + /* + * initialize child expressions + */ + scanstate->ss.ps.targetlist = (List *) + ExecInitExpr((Expr *) node->scan.plan.targetlist, + (PlanState *) scanstate); + scanstate->ss.ps.qual = (List *) + ExecInitExpr((Expr *) node->scan.plan.qual, + (PlanState *) scanstate); + + /* + * get info about values list + */ + rte = rt_fetch(node->scan.scanrelid, estate->es_range_table); + Assert(rte->rtekind == RTE_VALUES); + tupdesc = ExecTypeFromExprList((List *) linitial(rte->values_lists)); + + ExecAssignScanType(&scanstate->ss, tupdesc); + + /* + * Other node-specific setup + */ + scanstate->marked_idx = -1; + scanstate->curr_idx = -1; + scanstate->array_len = list_length(rte->values_lists); + + /* convert list of sublists into array of sublists for easy addressing */ + scanstate->exprlists = (List **) + palloc(scanstate->array_len * sizeof(List *)); + i = 0; + foreach(vtl, rte->values_lists) + { + scanstate->exprlists[i++] = (List *) lfirst(vtl); + } + + scanstate->ss.ps.ps_TupFromTlist = false; + + /* + * Initialize result tuple type and projection info. + */ + ExecAssignResultTypeFromTL(&scanstate->ss.ps); + ExecAssignScanProjectionInfo(&scanstate->ss); + + return scanstate; +} + +int +ExecCountSlotsValuesScan(ValuesScan *node) +{ + return ExecCountSlotsNode(outerPlan(node)) + + ExecCountSlotsNode(innerPlan(node)) + + VALUESSCAN_NSLOTS; +} + +/* ---------------------------------------------------------------- + * ExecEndValuesScan + * + * frees any storage allocated through C routines. + * ---------------------------------------------------------------- + */ +void +ExecEndValuesScan(ValuesScanState *node) +{ + /* + * Free the exprcontext + */ + ExecFreeExprContext(&node->ss.ps); + + /* + * clean out the tuple table + */ + ExecClearTuple(node->ss.ps.ps_ResultTupleSlot); + ExecClearTuple(node->ss.ss_ScanTupleSlot); +} + +/* ---------------------------------------------------------------- + * ExecValuesMarkPos + * + * Marks scan position. + * ---------------------------------------------------------------- + */ +void +ExecValuesMarkPos(ValuesScanState *node) +{ + node->marked_idx = node->curr_idx; +} + +/* ---------------------------------------------------------------- + * ExecValuesRestrPos + * + * Restores scan position. + * ---------------------------------------------------------------- + */ +void +ExecValuesRestrPos(ValuesScanState *node) +{ + node->curr_idx = node->marked_idx; +} + +/* ---------------------------------------------------------------- + * ExecValuesReScan + * + * Rescans the relation. + * ---------------------------------------------------------------- + */ +void +ExecValuesReScan(ValuesScanState *node, ExprContext *exprCtxt) +{ + ExecClearTuple(node->ss.ps.ps_ResultTupleSlot); + + node->curr_idx = -1; +} diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index f2b2afd81a..8903d6b42d 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.344 2006/07/27 19:52:05 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.345 2006/08/02 01:59:45 joe Exp $ * *------------------------------------------------------------------------- */ @@ -366,6 +366,22 @@ _copyFunctionScan(FunctionScan *from) return newnode; } +/* + * _copyValuesScan + */ +static ValuesScan * +_copyValuesScan(ValuesScan *from) +{ + ValuesScan *newnode = makeNode(ValuesScan); + + /* + * copy node superclass fields + */ + CopyScanFields((Scan *) from, (Scan *) newnode); + + return newnode; +} + /* * CopyJoinFields * @@ -1356,6 +1372,7 @@ _copyRangeTblEntry(RangeTblEntry *from) COPY_NODE_FIELD(funcexpr); COPY_NODE_FIELD(funccoltypes); COPY_NODE_FIELD(funccoltypmods); + COPY_NODE_FIELD(values_lists); COPY_SCALAR_FIELD(jointype); COPY_NODE_FIELD(joinaliasvars); COPY_NODE_FIELD(alias); @@ -1707,7 +1724,6 @@ _copyInsertStmt(InsertStmt *from) COPY_NODE_FIELD(relation); COPY_NODE_FIELD(cols); - COPY_NODE_FIELD(targetList); COPY_NODE_FIELD(selectStmt); return newnode; @@ -1754,6 +1770,7 @@ _copySelectStmt(SelectStmt *from) COPY_NODE_FIELD(whereClause); COPY_NODE_FIELD(groupClause); COPY_NODE_FIELD(havingClause); + COPY_NODE_FIELD(valuesLists); COPY_NODE_FIELD(sortClause); COPY_NODE_FIELD(limitOffset); COPY_NODE_FIELD(limitCount); @@ -2812,6 +2829,9 @@ copyObject(void *from) case T_FunctionScan: retval = _copyFunctionScan(from); break; + case T_ValuesScan: + retval = _copyValuesScan(from); + break; case T_Join: retval = _copyJoin(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 0122ebd629..d49e02b3d8 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -18,7 +18,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.278 2006/07/27 19:52:05 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.279 2006/08/02 01:59:45 joe Exp $ * *------------------------------------------------------------------------- */ @@ -682,7 +682,6 @@ _equalInsertStmt(InsertStmt *a, InsertStmt *b) { COMPARE_NODE_FIELD(relation); COMPARE_NODE_FIELD(cols); - COMPARE_NODE_FIELD(targetList); COMPARE_NODE_FIELD(selectStmt); return true; @@ -723,6 +722,7 @@ _equalSelectStmt(SelectStmt *a, SelectStmt *b) COMPARE_NODE_FIELD(whereClause); COMPARE_NODE_FIELD(groupClause); COMPARE_NODE_FIELD(havingClause); + COMPARE_NODE_FIELD(valuesLists); COMPARE_NODE_FIELD(sortClause); COMPARE_NODE_FIELD(limitOffset); COMPARE_NODE_FIELD(limitCount); @@ -1706,6 +1706,7 @@ _equalRangeTblEntry(RangeTblEntry *a, RangeTblEntry *b) COMPARE_NODE_FIELD(funcexpr); COMPARE_NODE_FIELD(funccoltypes); COMPARE_NODE_FIELD(funccoltypmods); + COMPARE_NODE_FIELD(values_lists); COMPARE_SCALAR_FIELD(jointype); COMPARE_NODE_FIELD(joinaliasvars); COMPARE_NODE_FIELD(alias); diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 61d49572ca..34555e1c56 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.279 2006/07/27 19:52:05 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.280 2006/08/02 01:59:45 joe Exp $ * * NOTES * Every node type that can appear in stored rules' parsetrees *must* @@ -410,6 +410,14 @@ _outFunctionScan(StringInfo str, FunctionScan *node) _outScanInfo(str, (Scan *) node); } +static void +_outValuesScan(StringInfo str, ValuesScan *node) +{ + WRITE_NODE_TYPE("VALUESSCAN"); + + _outScanInfo(str, (Scan *) node); +} + static void _outJoin(StringInfo str, Join *node) { @@ -1381,6 +1389,7 @@ _outSelectStmt(StringInfo str, SelectStmt *node) WRITE_NODE_FIELD(whereClause); WRITE_NODE_FIELD(groupClause); WRITE_NODE_FIELD(havingClause); + WRITE_NODE_FIELD(valuesLists); WRITE_NODE_FIELD(sortClause); WRITE_NODE_FIELD(limitOffset); WRITE_NODE_FIELD(limitCount); @@ -1591,6 +1600,9 @@ _outRangeTblEntry(StringInfo str, RangeTblEntry *node) WRITE_NODE_FIELD(funccoltypes); WRITE_NODE_FIELD(funccoltypmods); break; + case RTE_VALUES: + WRITE_NODE_FIELD(values_lists); + break; case RTE_JOIN: WRITE_ENUM_FIELD(jointype, JoinType); WRITE_NODE_FIELD(joinaliasvars); @@ -1876,6 +1888,9 @@ _outNode(StringInfo str, void *obj) case T_FunctionScan: _outFunctionScan(str, obj); break; + case T_ValuesScan: + _outValuesScan(str, obj); + break; case T_Join: _outJoin(str, obj); break; diff --git a/src/backend/nodes/print.c b/src/backend/nodes/print.c index 1a83be6574..7fff94a8a3 100644 --- a/src/backend/nodes/print.c +++ b/src/backend/nodes/print.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/print.c,v 1.80 2006/07/14 14:52:20 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/print.c,v 1.81 2006/08/02 01:59:45 joe Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -275,6 +275,10 @@ print_rt(List *rtable) printf("%d\t%s\t[rangefunction]", i, rte->eref->aliasname); break; + case RTE_VALUES: + printf("%d\t%s\t[values list]", + i, rte->eref->aliasname); + break; case RTE_JOIN: printf("%d\t%s\t[join]", i, rte->eref->aliasname); @@ -507,6 +511,8 @@ plannode_type(Plan *p) return "SUBQUERYSCAN"; case T_FunctionScan: return "FUNCTIONSCAN"; + case T_ValuesScan: + return "VALUESSCAN"; case T_Join: return "JOIN"; case T_NestLoop: @@ -575,6 +581,13 @@ print_plan_recursive(Plan *p, Query *parsetree, int indentLevel, char *label) rte = rt_fetch(((FunctionScan *) p)->scan.scanrelid, parsetree->rtable); StrNCpy(extraInfo, rte->eref->aliasname, NAMEDATALEN); } + else if (IsA(p, ValuesScan)) + { + RangeTblEntry *rte; + + rte = rt_fetch(((ValuesScan *) p)->scan.scanrelid, parsetree->rtable); + StrNCpy(extraInfo, rte->eref->aliasname, NAMEDATALEN); + } else extraInfo[0] = '\0'; if (extraInfo[0] != '\0') diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 265a5b369e..7459acb30c 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.192 2006/07/27 19:52:05 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.193 2006/08/02 01:59:45 joe Exp $ * * NOTES * Path and Plan nodes do not have any readfuncs support, because we @@ -902,6 +902,9 @@ _readRangeTblEntry(void) READ_NODE_FIELD(funccoltypes); READ_NODE_FIELD(funccoltypmods); break; + case RTE_VALUES: + READ_NODE_FIELD(values_lists); + break; case RTE_JOIN: READ_ENUM_FIELD(jointype, JoinType); READ_NODE_FIELD(joinaliasvars); diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 05fe268a31..1d7e20c1e8 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.149 2006/07/14 14:52:20 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/path/allpaths.c,v 1.150 2006/08/02 01:59:45 joe Exp $ * *------------------------------------------------------------------------- */ @@ -48,6 +48,8 @@ static void set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte); +static void set_values_pathlist(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte); static RelOptInfo *make_rel_from_joinlist(PlannerInfo *root, List *joinlist); static RelOptInfo *make_one_rel_by_joins(PlannerInfo *root, int levels_needed, List *initial_rels); @@ -170,6 +172,11 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti) /* RangeFunction --- generate a separate plan for it */ set_function_pathlist(root, rel, rte); } + else if (rel->rtekind == RTE_VALUES) + { + /* Values list --- generate a separate plan for it */ + set_values_pathlist(root, rel, rte); + } else { /* Plain relation */ @@ -537,6 +544,23 @@ set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) set_cheapest(rel); } +/* + * set_values_pathlist + * Build the (single) access path for a VALUES RTE + */ +static void +set_values_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) +{ + /* Mark rel with estimated output rows, width, etc */ + set_values_size_estimates(root, rel); + + /* Generate appropriate path */ + add_path(rel, create_valuesscan_path(root, rel)); + + /* Select cheapest path (pretty easy in this case...) */ + set_cheapest(rel); +} + /* * make_rel_from_joinlist * Build access paths using a "joinlist" to guide the join path search. diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c index 5f5e9ec372..fffa25dd84 100644 --- a/src/backend/optimizer/path/costsize.c +++ b/src/backend/optimizer/path/costsize.c @@ -54,7 +54,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.164 2006/07/26 11:35:56 petere Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/path/costsize.c,v 1.165 2006/08/02 01:59:45 joe Exp $ * *------------------------------------------------------------------------- */ @@ -774,6 +774,36 @@ cost_functionscan(Path *path, PlannerInfo *root, RelOptInfo *baserel) path->total_cost = startup_cost + run_cost; } +/* + * cost_valuesscan + * Determines and returns the cost of scanning a VALUES RTE. + */ +void +cost_valuesscan(Path *path, PlannerInfo *root, RelOptInfo *baserel) +{ + Cost startup_cost = 0; + Cost run_cost = 0; + Cost cpu_per_tuple; + + /* Should only be applied to base relations that are values lists */ + Assert(baserel->relid > 0); + Assert(baserel->rtekind == RTE_VALUES); + + /* + * For now, estimate list evaluation cost at one operator eval per + * list (probably pretty bogus, but is it worth being smarter?) + */ + cpu_per_tuple = cpu_operator_cost; + + /* Add scanning CPU costs */ + startup_cost += baserel->baserestrictcost.startup; + cpu_per_tuple += cpu_tuple_cost + baserel->baserestrictcost.per_tuple; + run_cost += cpu_per_tuple * baserel->tuples; + + path->startup_cost = startup_cost; + path->total_cost = startup_cost + run_cost; +} + /* * cost_sort * Determines and returns the cost of sorting a relation, including @@ -2023,6 +2053,37 @@ set_function_size_estimates(PlannerInfo *root, RelOptInfo *rel) set_baserel_size_estimates(root, rel); } +/* + * set_values_size_estimates + * Set the size estimates for a base relation that is a values list. + * + * The rel's targetlist and restrictinfo list must have been constructed + * already. + * + * We set the same fields as set_baserel_size_estimates. + */ +void +set_values_size_estimates(PlannerInfo *root, RelOptInfo *rel) +{ + RangeTblEntry *rte; + + /* Should only be applied to base relations that are values lists */ + Assert(rel->relid > 0); + rte = rt_fetch(rel->relid, root->parse->rtable); + Assert(rte->rtekind == RTE_VALUES); + + /* + * Estimate number of rows the values list will return. + * We know this precisely based on the list length (well, + * barring set-returning functions in list items, but that's + * a refinement not catered for anywhere else either). + */ + rel->tuples = list_length(rte->values_lists); + + /* Now estimate number of output rows, etc */ + set_baserel_size_estimates(root, rel); +} + /* * set_rel_width diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 7ed718f0b3..ae51505954 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.215 2006/07/26 00:34:48 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/plan/createplan.c,v 1.216 2006/08/02 01:59:45 joe Exp $ * *------------------------------------------------------------------------- */ @@ -59,6 +59,8 @@ static SubqueryScan *create_subqueryscan_plan(PlannerInfo *root, Path *best_path List *tlist, List *scan_clauses); static FunctionScan *create_functionscan_plan(PlannerInfo *root, Path *best_path, List *tlist, List *scan_clauses); +static ValuesScan *create_valuesscan_plan(PlannerInfo *root, Path *best_path, + List *tlist, List *scan_clauses); static NestLoop *create_nestloop_plan(PlannerInfo *root, NestPath *best_path, Plan *outer_plan, Plan *inner_plan); static MergeJoin *create_mergejoin_plan(PlannerInfo *root, MergePath *best_path, @@ -95,6 +97,8 @@ static TidScan *make_tidscan(List *qptlist, List *qpqual, Index scanrelid, List *tidquals); static FunctionScan *make_functionscan(List *qptlist, List *qpqual, Index scanrelid); +static ValuesScan *make_valuesscan(List *qptlist, List *qpqual, + Index scanrelid); static BitmapAnd *make_bitmap_and(List *bitmapplans); static BitmapOr *make_bitmap_or(List *bitmapplans); static NestLoop *make_nestloop(List *tlist, @@ -146,6 +150,7 @@ create_plan(PlannerInfo *root, Path *best_path) case T_TidScan: case T_SubqueryScan: case T_FunctionScan: + case T_ValuesScan: plan = create_scan_plan(root, best_path); break; case T_HashJoin: @@ -262,6 +267,13 @@ create_scan_plan(PlannerInfo *root, Path *best_path) scan_clauses); break; + case T_ValuesScan: + plan = (Plan *) create_valuesscan_plan(root, + best_path, + tlist, + scan_clauses); + break; + default: elog(ERROR, "unrecognized node type: %d", (int) best_path->pathtype); @@ -315,12 +327,13 @@ use_physical_tlist(RelOptInfo *rel) int i; /* - * We can do this for real relation scans, subquery scans, and function - * scans (but not for, eg, joins). + * We can do this for real relation scans, subquery scans, function + * scans, and values scans (but not for, eg, joins). */ if (rel->rtekind != RTE_RELATION && rel->rtekind != RTE_SUBQUERY && - rel->rtekind != RTE_FUNCTION) + rel->rtekind != RTE_FUNCTION && + rel->rtekind != RTE_VALUES) return false; /* @@ -365,6 +378,7 @@ disuse_physical_tlist(Plan *plan, Path *path) case T_TidScan: case T_SubqueryScan: case T_FunctionScan: + case T_ValuesScan: plan->targetlist = build_relation_tlist(path->parent); break; default: @@ -1312,6 +1326,35 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path, return scan_plan; } +/* + * create_valuesscan_plan + * Returns a valuesscan plan for the base relation scanned by 'best_path' + * with restriction clauses 'scan_clauses' and targetlist 'tlist'. + */ +static ValuesScan * +create_valuesscan_plan(PlannerInfo *root, Path *best_path, + List *tlist, List *scan_clauses) +{ + ValuesScan *scan_plan; + Index scan_relid = best_path->parent->relid; + + /* it should be a values base rel... */ + Assert(scan_relid > 0); + Assert(best_path->parent->rtekind == RTE_VALUES); + + /* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */ + scan_clauses = extract_actual_clauses(scan_clauses, false); + + /* Sort clauses into best execution order */ + scan_clauses = order_qual_clauses(root, scan_clauses); + + scan_plan = make_valuesscan(tlist, scan_clauses, scan_relid); + + copy_path_costsize(&scan_plan->scan.plan, best_path); + + return scan_plan; +} + /***************************************************************************** * * JOIN METHODS @@ -2123,6 +2166,24 @@ make_functionscan(List *qptlist, return node; } +static ValuesScan * +make_valuesscan(List *qptlist, + List *qpqual, + Index scanrelid) +{ + ValuesScan *node = makeNode(ValuesScan); + Plan *plan = &node->scan.plan; + + /* cost should be inserted by caller */ + plan->targetlist = qptlist; + plan->qual = qpqual; + plan->lefttree = NULL; + plan->righttree = NULL; + node->scan.scanrelid = scanrelid; + + return node; +} + Append * make_append(List *appendplans, bool isTarget, List *tlist) { diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index c02f6e195c..42ae15cd48 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.205 2006/07/26 19:31:50 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/plan/planner.c,v 1.206 2006/08/02 01:59:46 joe Exp $ * *------------------------------------------------------------------------- */ @@ -48,9 +48,10 @@ ParamListInfo PlannerBoundParamList = NULL; /* current boundParams */ #define EXPRKIND_QUAL 0 #define EXPRKIND_TARGET 1 #define EXPRKIND_RTFUNC 2 -#define EXPRKIND_LIMIT 3 -#define EXPRKIND_ININFO 4 -#define EXPRKIND_APPINFO 5 +#define EXPRKIND_VALUES 3 +#define EXPRKIND_LIMIT 4 +#define EXPRKIND_ININFO 5 +#define EXPRKIND_APPINFO 6 static Node *preprocess_expression(PlannerInfo *root, Node *expr, int kind); @@ -295,7 +296,7 @@ subquery_planner(Query *parse, double tuple_fraction, preprocess_expression(root, (Node *) root->append_rel_list, EXPRKIND_APPINFO); - /* Also need to preprocess expressions for function RTEs */ + /* Also need to preprocess expressions for function and values RTEs */ foreach(l, parse->rtable) { RangeTblEntry *rte = (RangeTblEntry *) lfirst(l); @@ -303,6 +304,10 @@ subquery_planner(Query *parse, double tuple_fraction, if (rte->rtekind == RTE_FUNCTION) rte->funcexpr = preprocess_expression(root, rte->funcexpr, EXPRKIND_RTFUNC); + else if (rte->rtekind == RTE_VALUES) + rte->values_lists = (List *) + preprocess_expression(root, (Node *) rte->values_lists, + EXPRKIND_VALUES); } /* @@ -418,8 +423,10 @@ preprocess_expression(PlannerInfo *root, Node *expr, int kind) * If the query has any join RTEs, replace join alias variables with * base-relation variables. We must do this before sublink processing, * else sublinks expanded out from join aliases wouldn't get processed. + * We can skip it in VALUES lists, however, since they can't contain + * any Vars at all. */ - if (root->hasJoinRTEs) + if (root->hasJoinRTEs && kind != EXPRKIND_VALUES) expr = flatten_join_alias_vars(root, expr); /* @@ -437,10 +444,14 @@ preprocess_expression(PlannerInfo *root, Node *expr, int kind) * and we will waste cycles copying the tree. Notice however that we * still must do it for quals (to get AND/OR flatness); and if we are in a * subquery we should not assume it will be done only once. + * + * For VALUES lists we never do this at all, again on the grounds that + * we should optimize for one-time evaluation. */ - if (root->parse->jointree->fromlist != NIL || - kind == EXPRKIND_QUAL || - PlannerQueryLevel > 1) + if (kind != EXPRKIND_VALUES && + (root->parse->jointree->fromlist != NIL || + kind == EXPRKIND_QUAL || + PlannerQueryLevel > 1)) expr = eval_const_expressions(expr); /* @@ -465,7 +476,7 @@ preprocess_expression(PlannerInfo *root, Node *expr, int kind) * SS_replace_correlation_vars ... */ - /* Replace uplevel vars with Param nodes */ + /* Replace uplevel vars with Param nodes (this IS possible in VALUES) */ if (PlannerQueryLevel > 1) expr = SS_replace_correlation_vars(expr); diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 2529c42ad9..11ef4765d8 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.122 2006/07/14 14:52:21 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/plan/setrefs.c,v 1.123 2006/08/02 01:59:46 joe Exp $ * *------------------------------------------------------------------------- */ @@ -186,6 +186,18 @@ set_plan_references(Plan *plan, List *rtable) fix_expr_references(plan, rte->funcexpr); } break; + case T_ValuesScan: + { + RangeTblEntry *rte; + + fix_expr_references(plan, (Node *) plan->targetlist); + fix_expr_references(plan, (Node *) plan->qual); + rte = rt_fetch(((ValuesScan *) plan)->scan.scanrelid, + rtable); + Assert(rte->rtekind == RTE_VALUES); + fix_expr_references(plan, (Node *) rte->values_lists); + } + break; case T_NestLoop: set_join_references((Join *) plan, rtable); fix_expr_references(plan, (Node *) plan->targetlist); @@ -522,6 +534,12 @@ adjust_plan_varnos(Plan *plan, int rtoffset) adjust_expr_varnos((Node *) plan->qual, rtoffset); /* rte was already fixed by set_subqueryscan_references */ break; + case T_ValuesScan: + ((ValuesScan *) plan)->scan.scanrelid += rtoffset; + adjust_expr_varnos((Node *) plan->targetlist, rtoffset); + adjust_expr_varnos((Node *) plan->qual, rtoffset); + /* rte was already fixed by set_subqueryscan_references */ + break; case T_NestLoop: adjust_expr_varnos((Node *) plan->targetlist, rtoffset); adjust_expr_varnos((Node *) plan->qual, rtoffset); diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index 0bc9a46c95..95e560478d 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/plan/subselect.c,v 1.110 2006/07/14 14:52:21 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/plan/subselect.c,v 1.111 2006/08/02 01:59:46 joe Exp $ * *------------------------------------------------------------------------- */ @@ -1090,6 +1090,17 @@ finalize_plan(Plan *plan, List *rtable, } break; + case T_ValuesScan: + { + RangeTblEntry *rte; + + rte = rt_fetch(((ValuesScan *) plan)->scan.scanrelid, + rtable); + Assert(rte->rtekind == RTE_VALUES); + finalize_primnode((Node *) rte->values_lists, &context); + } + break; + case T_Append: { ListCell *l; diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index dfc43149d3..5570b33f48 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.215 2006/07/27 19:52:05 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.216 2006/08/02 01:59:46 joe Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -3351,6 +3351,10 @@ range_table_walker(List *rtable, if (walker(rte->funcexpr, context)) return true; break; + case RTE_VALUES: + if (walker(rte->values_lists, context)) + return true; + break; } } return false; @@ -3917,6 +3921,9 @@ range_table_mutator(List *rtable, case RTE_FUNCTION: MUTATE(newrte->funcexpr, rte->funcexpr, Node *); break; + case RTE_VALUES: + MUTATE(newrte->values_lists, rte->values_lists, List *); + break; } newrt = lappend(newrt, newrte); } diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 631d6087d8..2cc79ed239 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/pathnode.c,v 1.131 2006/07/22 15:41:55 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/pathnode.c,v 1.132 2006/08/02 01:59:46 joe Exp $ * *------------------------------------------------------------------------- */ @@ -1082,6 +1082,25 @@ create_functionscan_path(PlannerInfo *root, RelOptInfo *rel) return pathnode; } +/* + * create_valuesscan_path + * Creates a path corresponding to a scan of a VALUES list, + * returning the pathnode. + */ +Path * +create_valuesscan_path(PlannerInfo *root, RelOptInfo *rel) +{ + Path *pathnode = makeNode(Path); + + pathnode->pathtype = T_ValuesScan; + pathnode->parent = rel; + pathnode->pathkeys = NIL; /* result is always unordered */ + + cost_valuesscan(pathnode, root, rel); + + return pathnode; +} + /* * create_nestloop_path * Creates a pathnode corresponding to a nestloop join between two diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index bafe1b6673..f74faa5c97 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/plancat.c,v 1.122 2006/07/31 20:09:04 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/plancat.c,v 1.123 2006/08/02 01:59:46 joe Exp $ * *------------------------------------------------------------------------- */ @@ -493,9 +493,9 @@ relation_excluded_by_constraints(RelOptInfo *rel, RangeTblEntry *rte) * For now, we don't apply the physical-tlist optimization when there are * dropped cols. * - * We also support building a "physical" tlist for subqueries and functions, - * since the same optimization can occur in SubqueryScan and FunctionScan - * nodes. + * We also support building a "physical" tlist for subqueries, functions, + * and values lists, since the same optimization can occur in SubqueryScan, + * FunctionScan, and ValuesScan nodes. */ List * build_physical_tlist(PlannerInfo *root, RelOptInfo *rel) @@ -594,6 +594,21 @@ build_physical_tlist(PlannerInfo *root, RelOptInfo *rel) } break; + case RTE_VALUES: + expandRTE(rte, varno, 0, false /* dropped not applicable */ , + NULL, &colvars); + foreach(l, colvars) + { + var = (Var *) lfirst(l); + + tlist = lappend(tlist, + makeTargetEntry((Expr *) var, + var->varattno, + NULL, + false)); + } + break; + default: /* caller error */ elog(ERROR, "unsupported RTE kind %d in build_physical_tlist", diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 8d06254a9f..545b125197 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/relnode.c,v 1.80 2006/07/31 20:09:04 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/relnode.c,v 1.81 2006/08/02 01:59:46 joe Exp $ * *------------------------------------------------------------------------- */ @@ -96,8 +96,13 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind) break; case RTE_SUBQUERY: case RTE_FUNCTION: - /* Subquery or function --- set up attr range and arrays */ - /* Note: 0 is included in range to support whole-row Vars */ + case RTE_VALUES: + /* + * Subquery, function, or values list --- set up attr range + * and arrays + * + * Note: 0 is included in range to support whole-row Vars + */ rel->min_attr = 0; rel->max_attr = list_length(rte->eref->colnames); rel->attr_needed = (Relids *) diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index b086afe8ca..4f7001b6f1 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.340 2006/07/14 14:52:21 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.341 2006/08/02 01:59:46 joe Exp $ * *------------------------------------------------------------------------- */ @@ -98,10 +98,13 @@ static Query *transformViewStmt(ParseState *pstate, ViewStmt *stmt, static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt); static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt, List **extras_before, List **extras_after); +static List *transformInsertRow(ParseState *pstate, List *exprlist, + List *stmtcols, List *icolumns, List *attrnos); static Query *transformIndexStmt(ParseState *pstate, IndexStmt *stmt); static Query *transformRuleStmt(ParseState *query, RuleStmt *stmt, List **extras_before, List **extras_after); static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt); +static Query *transformValuesClause(ParseState *pstate, SelectStmt *stmt); static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt); static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt); static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt); @@ -367,12 +370,16 @@ transformStmt(ParseState *pstate, Node *parseTree, break; case T_SelectStmt: - if (((SelectStmt *) parseTree)->op == SETOP_NONE) - result = transformSelectStmt(pstate, - (SelectStmt *) parseTree); - else - result = transformSetOperationStmt(pstate, - (SelectStmt *) parseTree); + { + SelectStmt *n = (SelectStmt *) parseTree; + + if (n->valuesLists) + result = transformValuesClause(pstate, n); + else if (n->op == SETOP_NONE) + result = transformSelectStmt(pstate, n); + else + result = transformSetOperationStmt(pstate, n); + } break; case T_DeclareCursorStmt: @@ -510,19 +517,30 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt, List **extras_before, List **extras_after) { Query *qry = makeNode(Query); - Query *selectQuery = NULL; + SelectStmt *selectStmt = (SelectStmt *) stmt->selectStmt; + List *exprList = NIL; + bool isGeneralSelect; List *sub_rtable; List *sub_relnamespace; List *sub_varnamespace; List *icolumns; List *attrnos; + RangeTblEntry *rte; + RangeTblRef *rtr; ListCell *icols; ListCell *attnos; - ListCell *tl; + ListCell *lc; qry->commandType = CMD_INSERT; pstate->p_is_insert = true; + /* + * We have three cases to deal with: DEFAULT VALUES (selectStmt == NULL), + * VALUES list, or general SELECT input. We special-case VALUES, both + * for efficiency and so we can handle DEFAULT specifications. + */ + isGeneralSelect = (selectStmt && selectStmt->valuesLists == NIL); + /* * If a non-nil rangetable/namespace was passed in, and we are doing * INSERT/SELECT, arrange to pass the rangetable/namespace down to the @@ -532,7 +550,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt, * The SELECT's joinlist is not affected however. We must do this before * adding the target table to the INSERT's rtable. */ - if (stmt->selectStmt) + if (isGeneralSelect) { sub_rtable = pstate->p_rtable; pstate->p_rtable = NIL; @@ -557,10 +575,23 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt, qry->resultRelation = setTargetTable(pstate, stmt->relation, false, false, ACL_INSERT); + /* Validate stmt->cols list, or build default list if no list given */ + icolumns = checkInsertTargets(pstate, stmt->cols, &attrnos); + Assert(list_length(icolumns) == list_length(attrnos)); + /* - * Is it INSERT ... SELECT or INSERT ... VALUES? + * Determine which variant of INSERT we have. */ - if (stmt->selectStmt) + if (selectStmt == NULL) + { + /* + * We have INSERT ... DEFAULT VALUES. We can handle this case by + * emitting an empty targetlist --- all columns will be defaulted + * when the planner expands the targetlist. + */ + exprList = NIL; + } + else if (isGeneralSelect) { /* * We make the sub-pstate a child of the outer pstate so that it can @@ -570,8 +601,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt, * see. */ ParseState *sub_pstate = make_parsestate(pstate); - RangeTblEntry *rte; - RangeTblRef *rtr; + Query *selectQuery; /* * Process the source SELECT. @@ -617,8 +647,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt, pstate->p_joinlist = lappend(pstate->p_joinlist, rtr); /*---------- - * Generate a targetlist for the INSERT that selects all the - * non-resjunk columns from the subquery. (We need this to be + * Generate an expression list for the INSERT that selects all the + * non-resjunk columns from the subquery. (INSERT's tlist must be * separate from the subquery's tlist because we may add columns, * insert datatype coercions, etc.) * @@ -630,10 +660,10 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt, * INSERT INTO foo SELECT 'bar', ... FROM baz *---------- */ - qry->targetList = NIL; - foreach(tl, selectQuery->targetList) + exprList = NIL; + foreach(lc, selectQuery->targetList) { - TargetEntry *tle = (TargetEntry *) lfirst(tl); + TargetEntry *tle = (TargetEntry *) lfirst(lc); Expr *expr; if (tle->resjunk) @@ -648,82 +678,220 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt, exprType((Node *) tle->expr), exprTypmod((Node *) tle->expr), 0); - tle = makeTargetEntry(expr, - (AttrNumber) pstate->p_next_resno++, - tle->resname, - false); - qry->targetList = lappend(qry->targetList, tle); + exprList = lappend(exprList, expr); } + + /* Prepare row for assignment to target table */ + exprList = transformInsertRow(pstate, exprList, + stmt->cols, + icolumns, attrnos); + } + else if (list_length(selectStmt->valuesLists) > 1) + { + /* + * Process INSERT ... VALUES with multiple VALUES sublists. + * We generate a VALUES RTE holding the transformed expression + * lists, and build up a targetlist containing Vars that reference + * the VALUES RTE. + */ + List *exprsLists = NIL; + int sublist_length = -1; + + foreach(lc, selectStmt->valuesLists) + { + List *sublist = (List *) lfirst(lc); + + /* Do basic expression transformation (same as a ROW() expr) */ + sublist = transformExpressionList(pstate, sublist); + + /* + * All the sublists must be the same length, *after* transformation + * (which might expand '*' into multiple items). The VALUES RTE + * can't handle anything different. + */ + if (sublist_length < 0) + { + /* Remember post-transformation length of first sublist */ + sublist_length = list_length(sublist); + } + else if (sublist_length != list_length(sublist)) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("VALUES lists must all be the same length"))); + } + + /* Prepare row for assignment to target table */ + sublist = transformInsertRow(pstate, sublist, + stmt->cols, + icolumns, attrnos); + + exprsLists = lappend(exprsLists, sublist); + } + + /* + * There mustn't have been any table references in the expressions, + * else strange things would happen, like Cartesian products of + * those tables with the VALUES list ... + */ + if (pstate->p_joinlist != NIL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("VALUES must not contain table references"))); + + /* + * Another thing we can't currently support is NEW/OLD references + * in rules --- seems we'd need something like SQL99's LATERAL + * construct to ensure that the values would be available while + * evaluating the VALUES RTE. This is a shame. FIXME + */ + if (contain_vars_of_level((Node *) exprsLists, 0)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("VALUES must not contain OLD or NEW references"))); + + /* + * Generate the VALUES RTE + */ + rte = addRangeTableEntryForValues(pstate, exprsLists, NULL, true); + rtr = makeNode(RangeTblRef); + /* assume new rte is at end */ + rtr->rtindex = list_length(pstate->p_rtable); + Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable)); + pstate->p_joinlist = lappend(pstate->p_joinlist, rtr); + + /* + * Generate list of Vars referencing the RTE + */ + expandRTE(rte, rtr->rtindex, 0, false, NULL, &exprList); } else { - /* - * For INSERT ... VALUES, transform the given list of values to form a - * targetlist for the INSERT. + /*---------- + * Process INSERT ... VALUES with a single VALUES sublist. + * We treat this separately for efficiency and for historical + * compatibility --- specifically, allowing table references, + * such as + * INSERT INTO foo VALUES(bar.*) + * + * The sublist is just computed directly as the Query's targetlist, + * with no VALUES RTE. So it works just like SELECT without FROM. + *---------- */ - qry->targetList = transformTargetList(pstate, stmt->targetList); + List *valuesLists = selectStmt->valuesLists; + + Assert(list_length(valuesLists) == 1); + + /* Do basic expression transformation (same as a ROW() expr) */ + exprList = transformExpressionList(pstate, + (List *) linitial(valuesLists)); + + /* Prepare row for assignment to target table */ + exprList = transformInsertRow(pstate, exprList, + stmt->cols, + icolumns, attrnos); } /* - * Now we are done with SELECT-like processing, and can get on with - * transforming the target list to match the INSERT target columns. - */ - - /* Prepare to assign non-conflicting resnos to resjunk attributes */ - if (pstate->p_next_resno <= pstate->p_target_relation->rd_rel->relnatts) - pstate->p_next_resno = pstate->p_target_relation->rd_rel->relnatts + 1; - - /* Validate stmt->cols list, or build default list if no list given */ - icolumns = checkInsertTargets(pstate, stmt->cols, &attrnos); - - /* - * Prepare columns for assignment to target table. + * Generate query's target list using the computed list of expressions. */ + qry->targetList = NIL; icols = list_head(icolumns); attnos = list_head(attrnos); - foreach(tl, qry->targetList) + foreach(lc, exprList) { - TargetEntry *tle = (TargetEntry *) lfirst(tl); + Expr *expr = (Expr *) lfirst(lc); ResTarget *col; - - if (icols == NULL || attnos == NULL) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("INSERT has more expressions than target columns"))); + TargetEntry *tle; col = (ResTarget *) lfirst(icols); Assert(IsA(col, ResTarget)); - Assert(!tle->resjunk); - updateTargetListEntry(pstate, tle, col->name, lfirst_int(attnos), - col->indirection, col->location); + tle = makeTargetEntry(expr, + (AttrNumber) lfirst_int(attnos), + col->name, + false); + qry->targetList = lappend(qry->targetList, tle); icols = lnext(icols); attnos = lnext(attnos); } - /* - * Ensure that the targetlist has the same number of entries that were - * present in the columns list. Don't do the check unless an explicit - * columns list was given, though. - */ - if (stmt->cols != NIL && (icols != NULL || attnos != NULL)) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("INSERT has more target columns than expressions"))); - /* done building the range table and jointree */ qry->rtable = pstate->p_rtable; qry->jointree = makeFromExpr(pstate->p_joinlist, NULL); qry->hasSubLinks = pstate->p_hasSubLinks; - qry->hasAggs = pstate->p_hasAggs; + /* aggregates not allowed (but subselects are okay) */ if (pstate->p_hasAggs) - parseCheckAggregates(pstate, qry); + ereport(ERROR, + (errcode(ERRCODE_GROUPING_ERROR), + errmsg("cannot use aggregate function in VALUES"))); return qry; } +/* + * Prepare an INSERT row for assignment to the target table. + * + * The row might be either a VALUES row, or variables referencing a + * sub-SELECT output. + */ +static List * +transformInsertRow(ParseState *pstate, List *exprlist, + List *stmtcols, List *icolumns, List *attrnos) +{ + List *result; + ListCell *lc; + ListCell *icols; + ListCell *attnos; + + /* + * Check length of expr list. It must not have more expressions than + * there are target columns. We allow fewer, but only if no explicit + * columns list was given (the remaining columns are implicitly + * defaulted). Note we must check this *after* transformation because + * that could expand '*' into multiple items. + */ + if (list_length(exprlist) > list_length(icolumns)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("INSERT has more expressions than target columns"))); + if (stmtcols != NIL && + list_length(exprlist) < list_length(icolumns)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("INSERT has more target columns than expressions"))); + + /* + * Prepare columns for assignment to target table. + */ + result = NIL; + icols = list_head(icolumns); + attnos = list_head(attrnos); + foreach(lc, exprlist) + { + Expr *expr = (Expr *) lfirst(lc); + ResTarget *col; + + col = (ResTarget *) lfirst(icols); + Assert(IsA(col, ResTarget)); + + expr = transformAssignedExpr(pstate, expr, + col->name, + lfirst_int(attnos), + col->indirection, + col->location); + + result = lappend(result, expr); + + icols = lnext(icols); + attnos = lnext(attnos); + } + + return result; +} + /* * transformCreateStmt - * transforms the "create table" statement @@ -1933,6 +2101,187 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt) return qry; } +/* + * transformValuesClause - + * transforms a VALUES clause that's being used as a standalone SELECT + * + * We build a Query containing a VALUES RTE, rather as if one had written + * SELECT * FROM (VALUES ...) + */ +static Query * +transformValuesClause(ParseState *pstate, SelectStmt *stmt) +{ + Query *qry = makeNode(Query); + List *exprsLists = NIL; + List **coltype_lists = NULL; + Oid *coltypes = NULL; + int sublist_length = -1; + List *newExprsLists; + RangeTblEntry *rte; + RangeTblRef *rtr; + ListCell *lc; + ListCell *lc2; + int i; + + qry->commandType = CMD_SELECT; + + /* Most SELECT stuff doesn't apply in a VALUES clause */ + Assert(stmt->distinctClause == NIL); + Assert(stmt->into == NULL); + Assert(stmt->intoColNames == NIL); + Assert(stmt->targetList == NIL); + Assert(stmt->fromClause == NIL); + Assert(stmt->whereClause == NULL); + Assert(stmt->groupClause == NIL); + Assert(stmt->havingClause == NULL); + Assert(stmt->op == SETOP_NONE); + + /* + * For each row of VALUES, transform the raw expressions and gather + * type information. This is also a handy place to reject DEFAULT + * nodes, which the grammar allows for simplicity. + */ + foreach(lc, stmt->valuesLists) + { + List *sublist = (List *) lfirst(lc); + + /* Do basic expression transformation (same as a ROW() expr) */ + sublist = transformExpressionList(pstate, sublist); + + /* + * All the sublists must be the same length, *after* transformation + * (which might expand '*' into multiple items). The VALUES RTE + * can't handle anything different. + */ + if (sublist_length < 0) + { + /* Remember post-transformation length of first sublist */ + sublist_length = list_length(sublist); + /* and allocate arrays for column-type info */ + coltype_lists = (List **) palloc0(sublist_length * sizeof(List *)); + coltypes = (Oid *) palloc0(sublist_length * sizeof(Oid)); + } + else if (sublist_length != list_length(sublist)) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("VALUES lists must all be the same length"))); + } + + exprsLists = lappend(exprsLists, sublist); + + i = 0; + foreach(lc2, sublist) + { + Node *col = (Node *) lfirst(lc2); + + if (IsA(col, SetToDefault)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("DEFAULT can only appear in a VALUES list within INSERT"))); + coltype_lists[i] = lappend_oid(coltype_lists[i], exprType(col)); + i++; + } + } + + /* + * Now resolve the common types of the columns, and coerce everything + * to those types. + */ + for (i = 0; i < sublist_length; i++) + { + coltypes[i] = select_common_type(coltype_lists[i], "VALUES"); + } + + newExprsLists = NIL; + foreach(lc, exprsLists) + { + List *sublist = (List *) lfirst(lc); + List *newsublist = NIL; + + i = 0; + foreach(lc2, sublist) + { + Node *col = (Node *) lfirst(lc2); + + col = coerce_to_common_type(pstate, col, coltypes[i], "VALUES"); + newsublist = lappend(newsublist, col); + i++; + } + + newExprsLists = lappend(newExprsLists, newsublist); + } + + /* + * Generate the VALUES RTE + */ + rte = addRangeTableEntryForValues(pstate, newExprsLists, NULL, true); + rtr = makeNode(RangeTblRef); + /* assume new rte is at end */ + rtr->rtindex = list_length(pstate->p_rtable); + Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable)); + pstate->p_joinlist = lappend(pstate->p_joinlist, rtr); + + /* + * Generate a targetlist as though expanding "*" + */ + Assert(pstate->p_next_resno == 1); + qry->targetList = expandRelAttrs(pstate, rte, rtr->rtindex, 0); + + /* + * The grammar does allow attaching ORDER BY, LIMIT, and FOR UPDATE + * to a VALUES, so cope. + */ + qry->sortClause = transformSortClause(pstate, + stmt->sortClause, + &qry->targetList, + true /* fix unknowns */ ); + + qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset, + "OFFSET"); + qry->limitCount = transformLimitClause(pstate, stmt->limitCount, + "LIMIT"); + + if (stmt->lockingClause) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("SELECT FOR UPDATE/SHARE cannot be applied to VALUES"))); + + /* + * There mustn't have been any table references in the expressions, + * else strange things would happen, like Cartesian products of + * those tables with the VALUES list. We have to check this after + * parsing ORDER BY et al since those could insert more junk. + */ + if (list_length(pstate->p_joinlist) != 1) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("VALUES must not contain table references"))); + + /* + * Another thing we can't currently support is NEW/OLD references + * in rules --- seems we'd need something like SQL99's LATERAL + * construct to ensure that the values would be available while + * evaluating the VALUES RTE. This is a shame. FIXME + */ + if (contain_vars_of_level((Node *) newExprsLists, 0)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("VALUES must not contain OLD or NEW references"))); + + qry->rtable = pstate->p_rtable; + qry->jointree = makeFromExpr(pstate->p_joinlist, NULL); + + qry->hasSubLinks = pstate->p_hasSubLinks; + /* aggregates not allowed (but subselects are okay) */ + if (pstate->p_hasAggs) + ereport(ERROR, + (errcode(ERRCODE_GROUPING_ERROR), + errmsg("cannot use aggregate function in VALUES"))); + + return qry; +} + /* * transformSetOperationsStmt - * transforms a set-operations tree @@ -2931,6 +3280,11 @@ transformLockingClause(Query *qry, LockingClause *lc) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("SELECT FOR UPDATE/SHARE cannot be applied to a function"))); break; + case RTE_VALUES: + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("SELECT FOR UPDATE/SHARE cannot be applied to VALUES"))); + break; default: elog(ERROR, "unrecognized RTE type: %d", (int) rte->rtekind); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 434f05bc4d..4586c77f8a 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.553 2006/07/31 01:16:37 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.554 2006/08/02 01:59:46 joe Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -173,7 +173,7 @@ static void doNegateFloat(Value *v); DropOwnedStmt ReassignOwnedStmt %type select_no_parens select_with_parens select_clause - simple_select + simple_select values_clause %type alter_column_default opclass_item alter_using %type add_drop @@ -238,7 +238,7 @@ static void doNegateFloat(Value *v); qualified_name_list any_name any_name_list any_operator expr_list attrs target_list update_target_list insert_column_list - insert_target_list def_list indirection opt_indirection + values_list def_list indirection opt_indirection group_clause TriggerFuncArgs select_limit opt_select_limit opclass_item_list transaction_mode_list_or_empty @@ -299,7 +299,7 @@ static void doNegateFloat(Value *v); %type when_clause_list %type sub_type %type OptCreateAs CreateAsList -%type CreateAsElement +%type CreateAsElement values_item %type NumericOnly FloatOnly IntegerOnly %type alias_clause %type sortby @@ -308,7 +308,7 @@ static void doNegateFloat(Value *v); %type joined_table %type relation_expr %type relation_expr_opt_alias -%type target_el insert_target_el update_target_el insert_column_item +%type target_el update_target_el insert_column_item %type Typename SimpleTypename ConstTypename GenericType Numeric opt_float @@ -5342,41 +5342,24 @@ InsertStmt: ; insert_rest: - VALUES '(' insert_target_list ')' + SelectStmt { $$ = makeNode(InsertStmt); $$->cols = NIL; - $$->targetList = $3; - $$->selectStmt = NULL; - } - | DEFAULT VALUES - { - $$ = makeNode(InsertStmt); - $$->cols = NIL; - $$->targetList = NIL; - $$->selectStmt = NULL; - } - | SelectStmt - { - $$ = makeNode(InsertStmt); - $$->cols = NIL; - $$->targetList = NIL; $$->selectStmt = $1; } - | '(' insert_column_list ')' VALUES '(' insert_target_list ')' - { - $$ = makeNode(InsertStmt); - $$->cols = $2; - $$->targetList = $6; - $$->selectStmt = NULL; - } | '(' insert_column_list ')' SelectStmt { $$ = makeNode(InsertStmt); $$->cols = $2; - $$->targetList = NIL; $$->selectStmt = $4; } + | DEFAULT VALUES + { + $$ = makeNode(InsertStmt); + $$->cols = NIL; + $$->selectStmt = NULL; + } ; insert_column_list: @@ -5629,6 +5612,7 @@ simple_select: n->havingClause = $8; $$ = (Node *)n; } + | values_clause { $$ = $1; } | select_clause UNION opt_all select_clause { $$ = makeSetOp(SETOP_UNION, $3, $1, $4); @@ -5848,6 +5832,32 @@ locked_rels_list: | /* EMPTY */ { $$ = NIL; } ; + +values_clause: + VALUES '(' values_list ')' + { + SelectStmt *n = makeNode(SelectStmt); + n->valuesLists = list_make1($3); + $$ = (Node *) n; + } + | values_clause ',' '(' values_list ')' + { + SelectStmt *n = (SelectStmt *) $1; + n->valuesLists = lappend(n->valuesLists, $4); + $$ = (Node *) n; + } + ; + +values_list: values_item { $$ = list_make1($1); } + | values_list ',' values_item { $$ = lappend($1, $3); } + ; + +values_item: + a_expr { $$ = (Node *) $1; } + | DEFAULT { $$ = (Node *) makeNode(SetToDefault); } + ; + + /***************************************************************************** * * clauses common to all Optimizable Stmts: @@ -5937,10 +5947,17 @@ table_ref: relation_expr * However, it does seem like a good idea to emit * an error message that's better than "syntax error". */ - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("subquery in FROM must have an alias"), - errhint("For example, FROM (SELECT ...) [AS] foo."))); + if (IsA($1, SelectStmt) && + ((SelectStmt *) $1)->valuesLists) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("VALUES in FROM must have an alias"), + errhint("For example, FROM (VALUES ...) [AS] foo."))); + else + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("subquery in FROM must have an alias"), + errhint("For example, FROM (SELECT ...) [AS] foo."))); $$ = NULL; } | select_with_parens alias_clause @@ -8176,30 +8193,6 @@ update_target_el: ; -insert_target_list: - insert_target_el { $$ = list_make1($1); } - | insert_target_list ',' insert_target_el { $$ = lappend($1, $3); } - ; - -insert_target_el: - a_expr - { - $$ = makeNode(ResTarget); - $$->name = NULL; - $$->indirection = NIL; - $$->val = (Node *)$1; - $$->location = @1; - } - | DEFAULT - { - $$ = makeNode(ResTarget); - $$->name = NULL; - $$->indirection = NIL; - $$->val = (Node *) makeNode(SetToDefault); - $$->location = @1; - } - ; - /***************************************************************************** * @@ -8656,7 +8649,6 @@ unreserved_keyword: | VACUUM | VALID | VALIDATOR - | VALUES | VARYING | VIEW | VOLATILE @@ -8715,6 +8707,7 @@ col_name_keyword: | TIMESTAMP | TREAT | TRIM + | VALUES | VARCHAR ; diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 11ef7e4f1e..6a7117e98b 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.195 2006/07/14 14:52:22 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.196 2006/08/02 01:59:46 joe Exp $ * *------------------------------------------------------------------------- */ @@ -1281,56 +1281,9 @@ static Node * transformRowExpr(ParseState *pstate, RowExpr *r) { RowExpr *newr = makeNode(RowExpr); - List *newargs = NIL; - ListCell *arg; /* Transform the field expressions */ - foreach(arg, r->args) - { - Node *e = (Node *) lfirst(arg); - Node *newe; - - /* - * Check for "something.*". Depending on the complexity of the - * "something", the star could appear as the last name in ColumnRef, - * or as the last indirection item in A_Indirection. - */ - if (IsA(e, ColumnRef)) - { - ColumnRef *cref = (ColumnRef *) e; - - if (strcmp(strVal(llast(cref->fields)), "*") == 0) - { - /* It is something.*, expand into multiple items */ - newargs = list_concat(newargs, - ExpandColumnRefStar(pstate, cref, - false)); - continue; - } - } - else if (IsA(e, A_Indirection)) - { - A_Indirection *ind = (A_Indirection *) e; - Node *lastitem = llast(ind->indirection); - - if (IsA(lastitem, String) && - strcmp(strVal(lastitem), "*") == 0) - { - /* It is something.*, expand into multiple items */ - newargs = list_concat(newargs, - ExpandIndirectionStar(pstate, ind, - false)); - continue; - } - } - - /* - * Not "something.*", so transform as a single expression - */ - newe = transformExpr(pstate, e); - newargs = lappend(newargs, newe); - } - newr->args = newargs; + newr->args = transformExpressionList(pstate, r->args); /* Barring later casting, we consider the type RECORD */ newr->row_typeid = RECORDOID; @@ -1526,6 +1479,15 @@ transformWholeRowRef(ParseState *pstate, char *schemaname, char *relname, sublevels_up); } break; + case RTE_VALUES: + toid = RECORDOID; + /* returns composite; same as relation case */ + result = (Node *) makeVar(vnum, + InvalidAttrNumber, + toid, + -1, + sublevels_up); + break; default: /* diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c index f1b6a45e7f..9c1b58704e 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.93 2006/07/14 14:52:22 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_node.c,v 1.94 2006/08/02 01:59:47 joe Exp $ * *------------------------------------------------------------------------- */ @@ -258,7 +258,7 @@ transformArraySubscripts(ParseState *pstate, /* * If doing an array store, coerce the source value to the right type. - * (This should agree with the coercion done by updateTargetListEntry.) + * (This should agree with the coercion done by transformAssignedExpr.) */ if (assignFrom != NULL) { diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index 10f71712ff..e9896be634 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.123 2006/04/30 18:30:39 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.124 2006/08/02 01:59:47 joe Exp $ * *------------------------------------------------------------------------- */ @@ -940,6 +940,75 @@ addRangeTableEntryForFunction(ParseState *pstate, return rte; } +/* + * Add an entry for a VALUES list to the pstate's range table (p_rtable). + * + * This is much like addRangeTableEntry() except that it makes a values RTE. + */ +RangeTblEntry * +addRangeTableEntryForValues(ParseState *pstate, + List *exprs, + Alias *alias, + bool inFromCl) +{ + RangeTblEntry *rte = makeNode(RangeTblEntry); + char *refname = alias ? alias->aliasname : pstrdup("*VALUES*"); + Alias *eref; + int numaliases; + int numcolumns; + + rte->rtekind = RTE_VALUES; + rte->relid = InvalidOid; + rte->subquery = NULL; + rte->values_lists = exprs; + rte->alias = alias; + + eref = alias ? copyObject(alias) : makeAlias(refname, NIL); + + /* fill in any unspecified alias columns */ + numcolumns = list_length((List *) linitial(exprs)); + numaliases = list_length(eref->colnames); + while (numaliases < numcolumns) + { + char attrname[64]; + + numaliases++; + snprintf(attrname, sizeof(attrname), "column%d", numaliases); + eref->colnames = lappend(eref->colnames, + makeString(pstrdup(attrname))); + } + if (numcolumns < numaliases) + ereport(ERROR, + (errcode(ERRCODE_INVALID_COLUMN_REFERENCE), + errmsg("VALUES lists \"%s\" have %d columns available but %d columns specified", + refname, numcolumns, numaliases))); + + rte->eref = eref; + + /*---------- + * Flags: + * - this RTE should be expanded to include descendant tables, + * - this RTE is in the FROM clause, + * - this RTE should be checked for appropriate access rights. + * + * Subqueries are never checked for access rights. + *---------- + */ + rte->inh = false; /* never true for values RTEs */ + rte->inFromCl = inFromCl; + rte->requiredPerms = 0; + rte->checkAsUser = InvalidOid; + + /* + * Add completed RTE to pstate's range table list, but not to join list + * nor namespace --- caller must do that if appropriate. + */ + if (pstate != NULL) + pstate->p_rtable = lappend(pstate->p_rtable, rte); + + return rte; +} + /* * Add an entry for a join to the pstate's range table (p_rtable). * @@ -1233,6 +1302,41 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up, } } break; + case RTE_VALUES: + { + /* Values RTE */ + ListCell *aliasp_item = list_head(rte->eref->colnames); + ListCell *lc; + + varattno = 0; + foreach(lc, (List *) linitial(rte->values_lists)) + { + Node *col = (Node *) lfirst(lc); + + varattno++; + if (colnames) + { + /* Assume there is one alias per column */ + char *label = strVal(lfirst(aliasp_item)); + + *colnames = lappend(*colnames, + makeString(pstrdup(label))); + aliasp_item = lnext(aliasp_item); + } + + if (colvars) + { + Var *varnode; + + varnode = makeVar(rtindex, varattno, + exprType(col), + exprTypmod(col), + sublevels_up); + *colvars = lappend(*colvars, varnode); + } + } + } + break; case RTE_JOIN: { /* Join RTE */ @@ -1569,6 +1673,20 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum, } } break; + case RTE_VALUES: + { + /* Values RTE --- get type info from first sublist */ + List *collist = (List *) linitial(rte->values_lists); + Node *col; + + if (attnum < 1 || attnum > list_length(collist)) + elog(ERROR, "values list %s does not have attribute %d", + rte->eref->aliasname, attnum); + col = (Node *) list_nth(collist, attnum-1); + *vartype = exprType(col); + *vartypmod = exprTypmod(col); + } + break; case RTE_JOIN: { /* @@ -1619,7 +1737,8 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum) } break; case RTE_SUBQUERY: - /* Subselect RTEs never have dropped columns */ + case RTE_VALUES: + /* Subselect and Values RTEs never have dropped columns */ result = false; break; case RTE_JOIN: diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 6f2e44f6e1..9258acccfb 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.146 2006/07/14 14:52:22 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_target.c,v 1.147 2006/08/02 01:59:47 joe Exp $ * *------------------------------------------------------------------------- */ @@ -42,7 +42,11 @@ static Node *transformAssignmentIndirection(ParseState *pstate, ListCell *indirection, Node *rhs, int location); +static List *ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref, + bool targetlist); static List *ExpandAllTables(ParseState *pstate); +static List *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind, + bool targetlist); static int FigureColnameInternal(Node *node, char **name); @@ -151,6 +155,69 @@ transformTargetList(ParseState *pstate, List *targetlist) } +/* + * transformExpressionList() + * + * This is the identical transformation to transformTargetList, except that + * the input list elements are bare expressions without ResTarget decoration, + * and the output elements are likewise just expressions without TargetEntry + * decoration. We use this for ROW() and VALUES() constructs. + */ +List * +transformExpressionList(ParseState *pstate, List *exprlist) +{ + List *result = NIL; + ListCell *lc; + + foreach(lc, exprlist) + { + Node *e = (Node *) lfirst(lc); + + /* + * Check for "something.*". Depending on the complexity of the + * "something", the star could appear as the last name in ColumnRef, + * or as the last indirection item in A_Indirection. + */ + if (IsA(e, ColumnRef)) + { + ColumnRef *cref = (ColumnRef *) e; + + if (strcmp(strVal(llast(cref->fields)), "*") == 0) + { + /* It is something.*, expand into multiple items */ + result = list_concat(result, + ExpandColumnRefStar(pstate, cref, + false)); + continue; + } + } + else if (IsA(e, A_Indirection)) + { + A_Indirection *ind = (A_Indirection *) e; + Node *lastitem = llast(ind->indirection); + + if (IsA(lastitem, String) && + strcmp(strVal(lastitem), "*") == 0) + { + /* It is something.*, expand into multiple items */ + result = list_concat(result, + ExpandIndirectionStar(pstate, ind, + false)); + continue; + } + } + + /* + * Not "something.*", so transform as a single expression + */ + result = lappend(result, + transformExpr(pstate, e)); + } + + return result; +} + + /* * markTargetListOrigins() * Mark targetlist columns that are simple Vars with the source @@ -229,6 +296,7 @@ markTargetListOrigin(ParseState *pstate, TargetEntry *tle, break; case RTE_SPECIAL: case RTE_FUNCTION: + case RTE_VALUES: /* not a simple relation, leave it unmarked */ break; } @@ -236,23 +304,26 @@ markTargetListOrigin(ParseState *pstate, TargetEntry *tle, /* - * updateTargetListEntry() - * This is used in INSERT and UPDATE statements only. It prepares a - * TargetEntry for assignment to a column of the target table. + * transformAssignedExpr() + * This is used in INSERT and UPDATE statements only. It prepares an + * expression for assignment to a column of the target table. * This includes coercing the given value to the target column's type * (if necessary), and dealing with any subfield names or subscripts - * attached to the target column itself. + * attached to the target column itself. The input expression has + * already been through transformExpr(). * * pstate parse state - * tle target list entry to be modified + * expr expression to be modified * colname target column name (ie, name of attribute to be assigned to) * attrno target attribute number * indirection subscripts/field names for target column, if any - * location error cursor position (should point at column name), or -1 + * location error cursor position, or -1 + * + * Returns the modified expression. */ -void -updateTargetListEntry(ParseState *pstate, - TargetEntry *tle, +Expr * +transformAssignedExpr(ParseState *pstate, + Expr *expr, char *colname, int attrno, List *indirection, @@ -281,9 +352,9 @@ updateTargetListEntry(ParseState *pstate, * or array element with DEFAULT, since there can't be any default for * portions of a column. */ - if (tle->expr && IsA(tle->expr, SetToDefault)) + if (expr && IsA(expr, SetToDefault)) { - SetToDefault *def = (SetToDefault *) tle->expr; + SetToDefault *def = (SetToDefault *) expr; def->typeId = attrtype; def->typeMod = attrtypmod; @@ -303,7 +374,7 @@ updateTargetListEntry(ParseState *pstate, } /* Now we can use exprType() safely. */ - type_id = exprType((Node *) tle->expr); + type_id = exprType((Node *) expr); /* * If there is indirection on the target column, prepare an array or @@ -334,7 +405,7 @@ updateTargetListEntry(ParseState *pstate, attrno); } - tle->expr = (Expr *) + expr = (Expr *) transformAssignmentIndirection(pstate, colVar, colname, @@ -342,7 +413,7 @@ updateTargetListEntry(ParseState *pstate, attrtype, attrtypmod, list_head(indirection), - (Node *) tle->expr, + (Node *) expr, location); } else @@ -351,13 +422,13 @@ updateTargetListEntry(ParseState *pstate, * For normal non-qualified target column, do type checking and * coercion. */ - tle->expr = (Expr *) + expr = (Expr *) coerce_to_target_type(pstate, - (Node *) tle->expr, type_id, + (Node *) expr, type_id, attrtype, attrtypmod, COERCION_ASSIGNMENT, COERCE_IMPLICIT_CAST); - if (tle->expr == NULL) + if (expr == NULL) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("column \"%s\" is of type %s" @@ -369,6 +440,41 @@ updateTargetListEntry(ParseState *pstate, parser_errposition(pstate, location))); } + return expr; +} + + +/* + * updateTargetListEntry() + * This is used in UPDATE statements only. It prepares an UPDATE + * TargetEntry for assignment to a column of the target table. + * This includes coercing the given value to the target column's type + * (if necessary), and dealing with any subfield names or subscripts + * attached to the target column itself. + * + * pstate parse state + * tle target list entry to be modified + * colname target column name (ie, name of attribute to be assigned to) + * attrno target attribute number + * indirection subscripts/field names for target column, if any + * location error cursor position (should point at column name), or -1 + */ +void +updateTargetListEntry(ParseState *pstate, + TargetEntry *tle, + char *colname, + int attrno, + List *indirection, + int location) +{ + /* Fix up expression as needed */ + tle->expr = transformAssignedExpr(pstate, + tle->expr, + colname, + attrno, + indirection, + location); + /* * Set the resno to identify the target column --- the rewriter and * planner depend on this. We also set the resname to identify the target @@ -379,6 +485,7 @@ updateTargetListEntry(ParseState *pstate, tle->resname = colname; } + /* * Process indirection (field selection or subscripting) of the target * column in INSERT/UPDATE. This routine recurses for multiple levels @@ -701,9 +808,10 @@ checkInsertTargets(ParseState *pstate, List *cols, List **attrnos) * This handles the case where '*' appears as the last or only name in a * ColumnRef. The code is shared between the case of foo.* at the top level * in a SELECT target list (where we want TargetEntry nodes in the result) - * and foo.* in a ROW() construct (where we want just bare expressions). + * and foo.* in a ROW() or VALUES() construct (where we want just bare + * expressions). */ -List * +static List * ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref, bool targetlist) { @@ -836,9 +944,9 @@ ExpandAllTables(ParseState *pstate) * This handles the case where '*' appears as the last item in A_Indirection. * The code is shared between the case of foo.* at the top level in a SELECT * target list (where we want TargetEntry nodes in the result) and foo.* in - * a ROW() construct (where we want just bare expressions). + * a ROW() or VALUES() construct (where we want just bare expressions). */ -List * +static List * ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind, bool targetlist) { @@ -996,11 +1104,12 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup) { case RTE_RELATION: case RTE_SPECIAL: + case RTE_VALUES: /* - * This case should not occur: a column of a table shouldn't have - * type RECORD. Fall through and fail (most likely) at the - * bottom. + * This case should not occur: a column of a table or values list + * shouldn't have type RECORD. Fall through and fail + * (most likely) at the bottom. */ break; case RTE_SUBQUERY: diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c index b6eac7417f..a12aea6c38 100644 --- a/src/backend/parser/parse_type.c +++ b/src/backend/parser/parse_type.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_type.c,v 1.82 2006/07/14 14:52:22 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_type.c,v 1.83 2006/08/02 01:59:47 joe Exp $ * *------------------------------------------------------------------------- */ @@ -426,6 +426,7 @@ parseTypeString(const char *str, Oid *type_id, int32 *typmod) stmt->whereClause != NULL || stmt->groupClause != NIL || stmt->havingClause != NULL || + stmt->valuesLists != NIL || stmt->sortClause != NIL || stmt->limitOffset != NULL || stmt->limitCount != NULL || diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index 42c965f3c2..286fc5b498 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.164 2006/07/14 14:52:22 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.165 2006/08/02 01:59:47 joe Exp $ * *------------------------------------------------------------------------- */ @@ -41,11 +41,14 @@ static Query *rewriteRuleAction(Query *parsetree, int rt_index, CmdType event); static List *adjustJoinTreeList(Query *parsetree, bool removert, int rt_index); -static void rewriteTargetList(Query *parsetree, Relation target_relation); +static void rewriteTargetList(Query *parsetree, Relation target_relation, + List **attrno_list); static TargetEntry *process_matched_tle(TargetEntry *src_tle, TargetEntry *prior_tle, const char *attrName); static Node *get_assignment_input(Node *node); +static void rewriteValuesRTE(RangeTblEntry *rte, Relation target_relation, + List *attrnos); static void markQueryForLocking(Query *qry, bool forUpdate, bool noWait, bool skipOldNew); static List *matchLocks(CmdType event, RuleLock *rulelocks, @@ -480,9 +483,15 @@ adjustJoinTreeList(Query *parsetree, bool removert, int rt_index) * references to NEW.foo will produce wrong or incomplete results. Item 3 * is not needed for rewriting, but will be needed by the planner, and we * can do it essentially for free while handling items 1 and 2. + * + * If attrno_list isn't NULL, we return an additional output besides the + * rewritten targetlist: an integer list of the assigned-to attnums, in + * order of the original tlist's non-junk entries. This is needed for + * processing VALUES RTEs. */ static void -rewriteTargetList(Query *parsetree, Relation target_relation) +rewriteTargetList(Query *parsetree, Relation target_relation, + List **attrno_list) { CmdType commandType = parsetree->commandType; TargetEntry **new_tles; @@ -494,6 +503,9 @@ rewriteTargetList(Query *parsetree, Relation target_relation) numattrs; ListCell *temp; + if (attrno_list) /* initialize optional result list */ + *attrno_list = NIL; + /* * We process the normal (non-junk) attributes by scanning the input tlist * once and transferring TLEs into an array, then scanning the array to @@ -519,6 +531,10 @@ rewriteTargetList(Query *parsetree, Relation target_relation) elog(ERROR, "bogus resno %d in targetlist", attrno); att_tup = target_relation->rd_att->attrs[attrno - 1]; + /* put attrno into attrno_list even if it's dropped */ + if (attrno_list) + *attrno_list = lappend_int(*attrno_list, attrno); + /* We can (and must) ignore deleted attributes */ if (att_tup->attisdropped) continue; @@ -820,7 +836,7 @@ build_column_default(Relation rel, int attrno) * generally be true already, but there seem to be some corner cases * involving domain defaults where it might not be true. This should match * the parser's processing of non-defaulted expressions --- see - * updateTargetListEntry(). + * transformAssignedExpr(). */ exprtype = exprType(expr); @@ -843,6 +859,111 @@ build_column_default(Relation rel, int attrno) } +/* Does VALUES RTE contain any SetToDefault items? */ +static bool +searchForDefault(RangeTblEntry *rte) +{ + ListCell *lc; + + foreach(lc, rte->values_lists) + { + List *sublist = (List *) lfirst(lc); + ListCell *lc2; + + foreach(lc2, sublist) + { + Node *col = (Node *) lfirst(lc2); + + if (IsA(col, SetToDefault)) + return true; + } + } + return false; +} + +/* + * When processing INSERT ... VALUES with a VALUES RTE (ie, multiple VALUES + * lists), we have to replace any DEFAULT items in the VALUES lists with + * the appropriate default expressions. The other aspects of rewriteTargetList + * need be applied only to the query's targetlist proper. + * + * Note that we currently can't support subscripted or field assignment + * in the multi-VALUES case. The targetlist will contain simple Vars + * referencing the VALUES RTE, and therefore process_matched_tle() will + * reject any such attempt with "multiple assignments to same column". + */ +static void +rewriteValuesRTE(RangeTblEntry *rte, Relation target_relation, List *attrnos) +{ + List *newValues; + ListCell *lc; + + /* + * Rebuilding all the lists is a pretty expensive proposition in a big + * VALUES list, and it's a waste of time if there aren't any DEFAULT + * placeholders. So first scan to see if there are any. + */ + if (!searchForDefault(rte)) + return; /* nothing to do */ + + /* Check list lengths (we can assume all the VALUES sublists are alike) */ + Assert(list_length(attrnos) == list_length(linitial(rte->values_lists))); + + newValues = NIL; + foreach(lc, rte->values_lists) + { + List *sublist = (List *) lfirst(lc); + List *newList = NIL; + ListCell *lc2; + ListCell *lc3; + + forboth(lc2, sublist, lc3, attrnos) + { + Node *col = (Node *) lfirst(lc2); + int attrno = lfirst_int(lc3); + + if (IsA(col, SetToDefault)) + { + Form_pg_attribute att_tup; + Node *new_expr; + + att_tup = target_relation->rd_att->attrs[attrno - 1]; + + if (!att_tup->attisdropped) + new_expr = build_column_default(target_relation, attrno); + else + new_expr = NULL; /* force a NULL if dropped */ + + /* + * If there is no default (ie, default is effectively NULL), + * we've got to explicitly set the column to NULL. + */ + if (!new_expr) + { + new_expr = (Node *) makeConst(att_tup->atttypid, + att_tup->attlen, + (Datum) 0, + true, /* isnull */ + att_tup->attbyval); + /* this is to catch a NOT NULL domain constraint */ + new_expr = coerce_to_domain(new_expr, + InvalidOid, -1, + att_tup->atttypid, + COERCE_IMPLICIT_CAST, + false, + false); + } + newList = lappend(newList, new_expr); + } + else + newList = lappend(newList, col); + } + newValues = lappend(newValues, newList); + } + rte->values_lists = newValues; +} + + /* * matchLocks - * match the list of locks and returns the matching rules @@ -1375,8 +1496,45 @@ RewriteQuery(Query *parsetree, List *rewrite_events) * form. This will be needed by the planner anyway, and doing it now * ensures that any references to NEW.field will behave sanely. */ - if (event == CMD_INSERT || event == CMD_UPDATE) - rewriteTargetList(parsetree, rt_entry_relation); + if (event == CMD_UPDATE) + rewriteTargetList(parsetree, rt_entry_relation, NULL); + else if (event == CMD_INSERT) + { + RangeTblEntry *values_rte = NULL; + + /* + * If it's an INSERT ... VALUES (...), (...), ... + * there will be a single RTE for the VALUES targetlists. + */ + if (list_length(parsetree->jointree->fromlist) == 1) + { + RangeTblRef *rtr = (RangeTblRef *) linitial(parsetree->jointree->fromlist); + + if (IsA(rtr, RangeTblRef)) + { + RangeTblEntry *rte = rt_fetch(rtr->rtindex, + parsetree->rtable); + + if (rte->rtekind == RTE_VALUES) + values_rte = rte; + } + } + + if (values_rte) + { + List *attrnos; + + /* Process the main targetlist ... */ + rewriteTargetList(parsetree, rt_entry_relation, &attrnos); + /* ... and the VALUES expression lists */ + rewriteValuesRTE(values_rte, rt_entry_relation, attrnos); + } + else + { + /* Process just the main targetlist */ + rewriteTargetList(parsetree, rt_entry_relation, NULL); + } + } /* * Collect and apply the appropriate rules. diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index a2254b6e48..8228fb2646 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -2,7 +2,7 @@ * ruleutils.c - Functions to convert stored expressions/querytrees * back to source text * - * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.229 2006/07/27 19:52:06 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.230 2006/08/02 01:59:47 joe Exp $ **********************************************************************/ #include "postgres.h" @@ -131,6 +131,7 @@ static void make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc, int prettyFlags); static void get_query_def(Query *query, StringInfo buf, List *parentnamespace, TupleDesc resultDesc, int prettyFlags, int startIndent); +static void get_values_def(List *values_lists, deparse_context *context); static void get_select_query_def(Query *query, deparse_context *context, TupleDesc resultDesc); static void get_insert_query_def(Query *query, deparse_context *context); @@ -172,7 +173,8 @@ static void get_from_clause_coldeflist(List *names, List *types, List *typmods, deparse_context *context); static void get_opclass_name(Oid opclass, Oid actual_datatype, StringInfo buf); -static Node *processIndirection(Node *node, deparse_context *context); +static Node *processIndirection(Node *node, deparse_context *context, + bool printit); static void printSubscripts(ArrayRef *aref, deparse_context *context); static char *generate_relation_name(Oid relid); static char *generate_function_name(Oid funcid, int nargs, Oid *argtypes); @@ -1800,6 +1802,50 @@ get_query_def(Query *query, StringInfo buf, List *parentnamespace, } } +/* ---------- + * get_values_def - Parse back a VALUES list + * ---------- + */ +static void +get_values_def(List *values_lists, deparse_context *context) +{ + StringInfo buf = context->buf; + bool first_list = true; + ListCell *vtl; + + appendStringInfoString(buf, "VALUES "); + + foreach(vtl, values_lists) + { + List *sublist = (List *) lfirst(vtl); + bool first_col = true; + ListCell *lc; + + if (first_list) + first_list = false; + else + appendStringInfoString(buf, ", "); + + appendStringInfoChar(buf, '('); + foreach(lc, sublist) + { + Node *col = (Node *) lfirst(lc); + + if (first_col) + first_col = false; + else + appendStringInfoChar(buf, ','); + + /* + * Strip any top-level nodes representing indirection assignments, + * then print the result. + */ + get_rule_expr(processIndirection(col, context, false), + context, false); + } + appendStringInfoChar(buf, ')'); + } +} /* ---------- * get_select_query_def - Parse back a SELECT parsetree @@ -1910,14 +1956,37 @@ get_basic_select_query(Query *query, deparse_context *context, ListCell *l; int colno; - /* - * Build up the query string - first we say SELECT - */ if (PRETTY_INDENT(context)) { context->indentLevel += PRETTYINDENT_STD; appendStringInfoChar(buf, ' '); } + + /* + * If the query looks like SELECT * FROM (VALUES ...), then print just + * the VALUES part. This reverses what transformValuesClause() did at + * parse time. If the jointree contains just a single VALUES RTE, + * we assume this case applies (without looking at the targetlist...) + */ + if (list_length(query->jointree->fromlist) == 1) + { + RangeTblRef *rtr = (RangeTblRef *) linitial(query->jointree->fromlist); + + if (IsA(rtr, RangeTblRef)) + { + RangeTblEntry *rte = rt_fetch(rtr->rtindex, query->rtable); + + if (rte->rtekind == RTE_VALUES) + { + get_values_def(rte->values_lists, context); + return; + } + } + } + + /* + * Build up the query string - first we say SELECT + */ appendStringInfo(buf, "SELECT"); /* Add the DISTINCT clause if given */ @@ -2191,24 +2260,37 @@ get_insert_query_def(Query *query, deparse_context *context) { StringInfo buf = context->buf; RangeTblEntry *select_rte = NULL; + RangeTblEntry *values_rte = NULL; RangeTblEntry *rte; char *sep; + ListCell *values_cell; ListCell *l; List *strippedexprs; /* - * If it's an INSERT ... SELECT there will be a single subquery RTE for - * the SELECT. + * If it's an INSERT ... SELECT or VALUES (...), (...), ... + * there will be a single RTE for the SELECT or VALUES. */ foreach(l, query->rtable) { rte = (RangeTblEntry *) lfirst(l); - if (rte->rtekind != RTE_SUBQUERY) - continue; - if (select_rte) - elog(ERROR, "too many RTEs in INSERT"); - select_rte = rte; + + if (rte->rtekind == RTE_SUBQUERY) + { + if (select_rte) + elog(ERROR, "too many subquery RTEs in INSERT"); + select_rte = rte; + } + + if (rte->rtekind == RTE_VALUES) + { + if (values_rte) + elog(ERROR, "too many values RTEs in INSERT"); + values_rte = rte; + } } + if (select_rte && values_rte) + elog(ERROR, "both subquery and values RTEs in INSERT"); /* * Start the query with INSERT INTO relname @@ -2225,9 +2307,17 @@ get_insert_query_def(Query *query, deparse_context *context) generate_relation_name(rte->relid)); /* - * Add the insert-column-names list, and make a list of the actual - * assignment source expressions. + * Add the insert-column-names list. To handle indirection properly, + * we need to look for indirection nodes in the top targetlist (if it's + * INSERT ... SELECT or INSERT ... single VALUES), or in the first + * expression list of the VALUES RTE (if it's INSERT ... multi VALUES). + * We assume that all the expression lists will have similar indirection + * in the latter case. */ + if (values_rte) + values_cell = list_head((List *) linitial(values_rte->values_lists)); + else + values_cell = NULL; strippedexprs = NIL; sep = ""; foreach(l, query->targetList) @@ -2252,23 +2342,41 @@ get_insert_query_def(Query *query, deparse_context *context) * Print any indirection needed (subfields or subscripts), and strip * off the top-level nodes representing the indirection assignments. */ - strippedexprs = lappend(strippedexprs, - processIndirection((Node *) tle->expr, - context)); + if (values_cell) + { + /* we discard the stripped expression in this case */ + processIndirection((Node *) lfirst(values_cell), context, true); + values_cell = lnext(values_cell); + } + else + { + /* we keep a list of the stripped expressions in this case */ + strippedexprs = lappend(strippedexprs, + processIndirection((Node *) tle->expr, + context, true)); + } } appendStringInfo(buf, ") "); - /* Add the VALUES or the SELECT */ - if (select_rte == NULL) + if (select_rte) { + /* Add the SELECT */ + get_query_def(select_rte->subquery, buf, NIL, NULL, + context->prettyFlags, context->indentLevel); + } + else if (values_rte) + { + /* Add the multi-VALUES expression lists */ + get_values_def(values_rte->values_lists, context); + } + else + { + /* Add the single-VALUES expression list */ appendContextKeyword(context, "VALUES (", -PRETTYINDENT_STD, PRETTYINDENT_STD, 2); get_rule_expr((Node *) strippedexprs, context, false); appendStringInfoChar(buf, ')'); } - else - get_query_def(select_rte->subquery, buf, NIL, NULL, - context->prettyFlags, context->indentLevel); } @@ -2323,7 +2431,7 @@ get_update_query_def(Query *query, deparse_context *context) * Print any indirection needed (subfields or subscripts), and strip * off the top-level nodes representing the indirection assignments. */ - expr = processIndirection((Node *) tle->expr, context); + expr = processIndirection((Node *) tle->expr, context, true); appendStringInfo(buf, " = "); @@ -2612,11 +2720,12 @@ get_name_for_var_field(Var *var, int fieldno, switch (rte->rtekind) { case RTE_RELATION: + case RTE_VALUES: /* - * This case should not occur: a column of a table shouldn't have - * type RECORD. Fall through and fail (most likely) at the - * bottom. + * This case should not occur: a column of a table or values list + * shouldn't have type RECORD. Fall through and fail + * (most likely) at the bottom. */ break; case RTE_SUBQUERY: @@ -4232,6 +4341,10 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context) /* Function RTE */ get_rule_expr(rte->funcexpr, context, true); break; + case RTE_VALUES: + /* Values list RTE */ + get_values_def(rte->values_lists, context); + break; default: elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind); break; @@ -4576,12 +4689,12 @@ get_opclass_name(Oid opclass, Oid actual_datatype, * processIndirection - take care of array and subfield assignment * * We strip any top-level FieldStore or assignment ArrayRef nodes that - * appear in the input, printing out the appropriate decoration for the - * base column name (that the caller just printed). We return the - * subexpression that's to be assigned. + * appear in the input, and return the subexpression that's to be assigned. + * If printit is true, we also print out the appropriate decoration for the + * base column name (that the caller just printed). */ static Node * -processIndirection(Node *node, deparse_context *context) +processIndirection(Node *node, deparse_context *context, bool printit) { StringInfo buf = context->buf; @@ -4602,15 +4715,16 @@ processIndirection(Node *node, deparse_context *context) format_type_be(fstore->resulttype)); /* - * Get the field name. Note we assume here that there's only one - * field being assigned to. This is okay in stored rules but + * Print the field name. Note we assume here that there's only + * one field being assigned to. This is okay in stored rules but * could be wrong in executable target lists. Presently no * problem since explain.c doesn't print plan targetlists, but * someday may have to think of something ... */ fieldname = get_relid_attribute_name(typrelid, linitial_int(fstore->fieldnums)); - appendStringInfo(buf, ".%s", quote_identifier(fieldname)); + if (printit) + appendStringInfo(buf, ".%s", quote_identifier(fieldname)); /* * We ignore arg since it should be an uninteresting reference to @@ -4624,7 +4738,8 @@ processIndirection(Node *node, deparse_context *context) if (aref->refassgnexpr == NULL) break; - printSubscripts(aref, context); + if (printit) + printSubscripts(aref, context); /* * We ignore refexpr since it should be an uninteresting reference diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 23b0ac9ce4..8d93308fce 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.345 2006/07/31 20:09:05 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.346 2006/08/02 01:59:47 joe Exp $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200607311 +#define CATALOG_VERSION_NO 200608011 #endif diff --git a/src/include/executor/nodeValuesscan.h b/src/include/executor/nodeValuesscan.h new file mode 100644 index 0000000000..5a952bdd32 --- /dev/null +++ b/src/include/executor/nodeValuesscan.h @@ -0,0 +1,27 @@ +/*------------------------------------------------------------------------- + * + * nodeValuesscan.h + * + * + * + * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * $PostgreSQL: pgsql/src/include/executor/nodeValuesscan.h,v 1.1 2006/08/02 01:59:47 joe Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef NODEVALUESSCAN_H +#define NODEVALUESSCAN_H + +#include "nodes/execnodes.h" + +extern int ExecCountSlotsValuesScan(ValuesScan *node); +extern ValuesScanState *ExecInitValuesScan(ValuesScan *node, EState *estate, int eflags); +extern TupleTableSlot *ExecValuesScan(ValuesScanState *node); +extern void ExecEndValuesScan(ValuesScanState *node); +extern void ExecValuesMarkPos(ValuesScanState *node); +extern void ExecValuesRestrPos(ValuesScanState *node); +extern void ExecValuesReScan(ValuesScanState *node, ExprContext *exprCtxt); + +#endif /* NODEVALUESSCAN_H */ diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 8dec4130e2..2e98124003 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.155 2006/07/27 19:52:07 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.156 2006/08/02 01:59:47 joe Exp $ * *------------------------------------------------------------------------- */ @@ -1041,6 +1041,27 @@ typedef struct FunctionScanState ExprState *funcexpr; } FunctionScanState; +/* ---------------- + * ValuesScanState information + * + * Values nodes are used to scan the results of a + * values list appearing in FROM or INSERT + * + * exprlists array of expression lists being evaluated + * array_len size of array + * curr_idx current array index (0-based) + * marked_idx marked position (for mark/restore) + * ---------------- + */ +typedef struct ValuesScanState +{ + ScanState ss; /* its first field is NodeTag */ + List **exprlists; + int array_len; + int curr_idx; + int marked_idx; +} ValuesScanState; + /* ---------------------------------------------------------------- * Join State Information * ---------------------------------------------------------------- diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 53f3ee1d61..eb31fd2b6e 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.186 2006/04/30 18:30:40 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/nodes.h,v 1.187 2006/08/02 01:59:47 joe Exp $ * *------------------------------------------------------------------------- */ @@ -54,6 +54,7 @@ typedef enum NodeTag T_TidScan, T_SubqueryScan, T_FunctionScan, + T_ValuesScan, T_Join, T_NestLoop, T_MergeJoin, @@ -85,6 +86,7 @@ typedef enum NodeTag T_TidScanState, T_SubqueryScanState, T_FunctionScanState, + T_ValuesScanState, T_JoinState, T_NestLoopState, T_MergeJoinState, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 5c227156f0..d0fa16ff51 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.319 2006/07/31 01:16:38 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.320 2006/08/02 01:59:47 joe Exp $ * *------------------------------------------------------------------------- */ @@ -303,13 +303,13 @@ typedef struct A_Indirection * ResTarget - * result target (used in target list of pre-transformed parse trees) * - * In a SELECT or INSERT target list, 'name' is the column label from an + * In a SELECT target list, 'name' is the column label from an * 'AS ColumnLabel' clause, or NULL if there was none, and 'val' is the * value expression itself. The 'indirection' field is not used. * - * INSERT has a second ResTarget list which is the target-column-names list. - * Here, 'val' is not used, 'name' is the name of the destination column, - * and 'indirection' stores any subscripts attached to the destination. + * INSERT uses ResTarget in its target-column-names list. Here, 'name' is + * the name of the destination column, 'indirection' stores any subscripts + * attached to the destination, and 'val' is not used. * * In an UPDATE target list, 'name' is the name of the destination column, * 'indirection' stores any subscripts attached to the destination, and @@ -517,7 +517,8 @@ typedef enum RTEKind RTE_SUBQUERY, /* subquery in FROM */ RTE_JOIN, /* join */ RTE_SPECIAL, /* special rule relation (NEW or OLD) */ - RTE_FUNCTION /* function in FROM */ + RTE_FUNCTION, /* function in FROM */ + RTE_VALUES /* VALUES (), (), ... */ } RTEKind; typedef struct RangeTblEntry @@ -553,6 +554,11 @@ typedef struct RangeTblEntry List *funccoltypes; /* OID list of column type OIDs */ List *funccoltypmods; /* integer list of column typmods */ + /* + * Fields valid for a values RTE (else NIL): + */ + List *values_lists; /* list of expression lists */ + /* * Fields valid for a join RTE (else NULL/zero): * @@ -630,6 +636,10 @@ typedef struct RowMarkClause /* ---------------------- * Insert Statement + * + * The source expression is represented by SelectStmt for both the + * SELECT and VALUES cases. If selectStmt is NULL, then the query + * is INSERT ... DEFAULT VALUES. * ---------------------- */ typedef struct InsertStmt @@ -637,14 +647,7 @@ typedef struct InsertStmt NodeTag type; RangeVar *relation; /* relation to insert into */ List *cols; /* optional: names of the target columns */ - - /* - * An INSERT statement has *either* VALUES or SELECT, never both. If - * VALUES, a targetList is supplied (empty for DEFAULT VALUES). If SELECT, - * a complete SelectStmt (or set-operation tree) is supplied. - */ - List *targetList; /* the target list (of ResTarget) */ - Node *selectStmt; /* the source SELECT */ + Node *selectStmt; /* the source SELECT/VALUES, or NULL */ } InsertStmt; /* ---------------------- @@ -676,9 +679,9 @@ typedef struct UpdateStmt * Select Statement * * A "simple" SELECT is represented in the output of gram.y by a single - * SelectStmt node. A SELECT construct containing set operators (UNION, - * INTERSECT, EXCEPT) is represented by a tree of SelectStmt nodes, in - * which the leaf nodes are component SELECTs and the internal nodes + * SelectStmt node; so is a VALUES construct. A query containing set + * operators (UNION, INTERSECT, EXCEPT) is represented by a tree of SelectStmt + * nodes, in which the leaf nodes are component SELECTs and the internal nodes * represent UNION, INTERSECT, or EXCEPT operators. Using the same node * type for both leaf and internal nodes allows gram.y to stick ORDER BY, * LIMIT, etc, clause values into a SELECT statement without worrying @@ -716,6 +719,16 @@ typedef struct SelectStmt List *groupClause; /* GROUP BY clauses */ Node *havingClause; /* HAVING conditional-expression */ + /* + * In a "leaf" node representing a VALUES list, the above fields are all + * null, and instead this field is set. Note that the elements of + * the sublists are just expressions, without ResTarget decoration. + * Also note that a list element can be DEFAULT (represented as a + * SetToDefault node), regardless of the context of the VALUES list. + * It's up to parse analysis to reject that where not valid. + */ + List *valuesLists; /* untransformed list of expression lists */ + /* * These fields are used in both "leaf" SelectStmts and upper-level * SelectStmts. diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index 78a472342b..5c2de28a41 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/plannodes.h,v 1.84 2006/07/26 19:31:51 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/plannodes.h,v 1.85 2006/08/02 01:59:47 joe Exp $ * *------------------------------------------------------------------------- */ @@ -296,6 +296,16 @@ typedef struct FunctionScan /* no other fields needed at present */ } FunctionScan; +/* ---------------- + * ValuesScan node + * ---------------- + */ +typedef struct ValuesScan +{ + Scan scan; + /* no other fields needed at present */ +} ValuesScan; + /* * ========== * Join nodes diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h index 27928b89a5..3038857111 100644 --- a/src/include/optimizer/cost.h +++ b/src/include/optimizer/cost.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/optimizer/cost.h,v 1.78 2006/07/26 11:35:56 petere Exp $ + * $PostgreSQL: pgsql/src/include/optimizer/cost.h,v 1.79 2006/08/02 01:59:48 joe Exp $ * *------------------------------------------------------------------------- */ @@ -70,6 +70,8 @@ extern void cost_tidscan(Path *path, PlannerInfo *root, extern void cost_subqueryscan(Path *path, RelOptInfo *baserel); extern void cost_functionscan(Path *path, PlannerInfo *root, RelOptInfo *baserel); +extern void cost_valuesscan(Path *path, PlannerInfo *root, + RelOptInfo *baserel); extern void cost_sort(Path *path, PlannerInfo *root, List *pathkeys, Cost input_cost, double tuples, int width); extern void cost_material(Path *path, @@ -94,6 +96,7 @@ extern void set_joinrel_size_estimates(PlannerInfo *root, RelOptInfo *rel, JoinType jointype, List *restrictlist); extern void set_function_size_estimates(PlannerInfo *root, RelOptInfo *rel); +extern void set_values_size_estimates(PlannerInfo *root, RelOptInfo *rel); /* * prototypes for clausesel.c diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h index 71426b5706..e6005094b7 100644 --- a/src/include/optimizer/pathnode.h +++ b/src/include/optimizer/pathnode.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/optimizer/pathnode.h,v 1.70 2006/07/22 15:41:56 tgl Exp $ + * $PostgreSQL: pgsql/src/include/optimizer/pathnode.h,v 1.71 2006/08/02 01:59:48 joe Exp $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,7 @@ extern UniquePath *create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath); extern Path *create_subqueryscan_path(RelOptInfo *rel, List *pathkeys); extern Path *create_functionscan_path(PlannerInfo *root, RelOptInfo *rel); +extern Path *create_valuesscan_path(PlannerInfo *root, RelOptInfo *rel); extern NestPath *create_nestloop_path(PlannerInfo *root, RelOptInfo *joinrel, diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h index 33ebad8abc..9e2df63c54 100644 --- a/src/include/parser/parse_relation.h +++ b/src/include/parser/parse_relation.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/parser/parse_relation.h,v 1.53 2006/03/14 22:48:22 tgl Exp $ + * $PostgreSQL: pgsql/src/include/parser/parse_relation.h,v 1.54 2006/08/02 01:59:48 joe Exp $ * *------------------------------------------------------------------------- */ @@ -59,6 +59,10 @@ extern RangeTblEntry *addRangeTableEntryForFunction(ParseState *pstate, Node *funcexpr, RangeFunction *rangefunc, bool inFromCl); +extern RangeTblEntry *addRangeTableEntryForValues(ParseState *pstate, + List *exprs, + Alias *alias, + bool inFromCl); extern RangeTblEntry *addRangeTableEntryForJoin(ParseState *pstate, List *colnames, JoinType jointype, diff --git a/src/include/parser/parse_target.h b/src/include/parser/parse_target.h index 6080696baa..bbd154efe3 100644 --- a/src/include/parser/parse_target.h +++ b/src/include/parser/parse_target.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/parser/parse_target.h,v 1.40 2006/06/26 17:24:41 tgl Exp $ + * $PostgreSQL: pgsql/src/include/parser/parse_target.h,v 1.41 2006/08/02 01:59:48 joe Exp $ * *------------------------------------------------------------------------- */ @@ -18,14 +18,16 @@ extern List *transformTargetList(ParseState *pstate, List *targetlist); +extern List *transformExpressionList(ParseState *pstate, List *exprlist); extern void markTargetListOrigins(ParseState *pstate, List *targetlist); extern TargetEntry *transformTargetEntry(ParseState *pstate, Node *node, Node *expr, char *colname, bool resjunk); -extern List *ExpandColumnRefStar(ParseState *pstate, ColumnRef *cref, - bool targetlist); -extern List *ExpandIndirectionStar(ParseState *pstate, A_Indirection *ind, - bool targetlist); +extern Expr *transformAssignedExpr(ParseState *pstate, Expr *expr, + char *colname, + int attrno, + List *indirection, + int location); extern void updateTargetListEntry(ParseState *pstate, TargetEntry *tle, char *colname, int attrno, List *indirection,