aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJoe Conway <mail@joeconway.com>2006-08-02 01:59:48 +0000
committerJoe Conway <mail@joeconway.com>2006-08-02 01:59:48 +0000
commit9caafda579f699b43fa4c89bf13a2331ef00611e (patch)
tree330423c4be56ffaacb2d028153706f0c213c0aec /src
parentd307c428cbb7c426e40163d234d993e644bbcc6b (diff)
downloadpostgresql-9caafda579f699b43fa4c89bf13a2331ef00611e.tar.gz
postgresql-9caafda579f699b43fa4c89bf13a2331ef00611e.zip
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.
Diffstat (limited to 'src')
-rw-r--r--src/backend/catalog/heap.c4
-rw-r--r--src/backend/commands/explain.c22
-rw-r--r--src/backend/executor/Makefile5
-rw-r--r--src/backend/executor/execAmi.c22
-rw-r--r--src/backend/executor/execProcnode.c19
-rw-r--r--src/backend/executor/nodeValuesscan.c332
-rw-r--r--src/backend/nodes/copyfuncs.c24
-rw-r--r--src/backend/nodes/equalfuncs.c5
-rw-r--r--src/backend/nodes/outfuncs.c17
-rw-r--r--src/backend/nodes/print.c15
-rw-r--r--src/backend/nodes/readfuncs.c5
-rw-r--r--src/backend/optimizer/path/allpaths.c26
-rw-r--r--src/backend/optimizer/path/costsize.c63
-rw-r--r--src/backend/optimizer/plan/createplan.c69
-rw-r--r--src/backend/optimizer/plan/planner.c31
-rw-r--r--src/backend/optimizer/plan/setrefs.c20
-rw-r--r--src/backend/optimizer/plan/subselect.c13
-rw-r--r--src/backend/optimizer/util/clauses.c9
-rw-r--r--src/backend/optimizer/util/pathnode.c21
-rw-r--r--src/backend/optimizer/util/plancat.c23
-rw-r--r--src/backend/optimizer/util/relnode.c11
-rw-r--r--src/backend/parser/analyze.c474
-rw-r--r--src/backend/parser/gram.y107
-rw-r--r--src/backend/parser/parse_expr.c60
-rw-r--r--src/backend/parser/parse_node.c4
-rw-r--r--src/backend/parser/parse_relation.c123
-rw-r--r--src/backend/parser/parse_target.c159
-rw-r--r--src/backend/parser/parse_type.c3
-rw-r--r--src/backend/rewrite/rewriteHandler.c170
-rw-r--r--src/backend/utils/adt/ruleutils.c183
-rw-r--r--src/include/catalog/catversion.h4
-rw-r--r--src/include/executor/nodeValuesscan.h27
-rw-r--r--src/include/nodes/execnodes.h23
-rw-r--r--src/include/nodes/nodes.h4
-rw-r--r--src/include/nodes/parsenodes.h47
-rw-r--r--src/include/nodes/plannodes.h12
-rw-r--r--src/include/optimizer/cost.h5
-rw-r--r--src/include/optimizer/pathnode.h3
-rw-r--r--src/include/parser/parse_relation.h6
-rw-r--r--src/include/parser/parse_target.h12
40 files changed, 1873 insertions, 309 deletions
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 922a0cebf71..e1f3e194b63 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 7c50784abad..5787aa413e7 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 e9caf7d8373..7bc8eac7f51 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 df692d9bdfd..1d5246f9ecd 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 77f9f2e42ed..9f1fd54a8e5 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 00000000000..eb053d8cc76
--- /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 f2b2afd81af..8903d6b42dd 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 $
*
*-------------------------------------------------------------------------
*/
@@ -367,6 +367,22 @@ _copyFunctionScan(FunctionScan *from)
}
/*
+ * _copyValuesScan
+ */
+static ValuesScan *
+_copyValuesScan(ValuesScan *from)
+{
+ ValuesScan *newnode = makeNode(ValuesScan);
+
+ /*
+ * copy node superclass fields
+ */
+ CopyScanFields((Scan *) from, (Scan *) newnode);
+
+ return newnode;
+}
+
+/*
* CopyJoinFields
*
* This function copies the fields of the Join node. It is used by
@@ -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 0122ebd629c..d49e02b3d8a 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 61d49572ca8..34555e1c567 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*
@@ -411,6 +411,14 @@ _outFunctionScan(StringInfo str, FunctionScan *node)
}
static void
+_outValuesScan(StringInfo str, ValuesScan *node)
+{
+ WRITE_NODE_TYPE("VALUESSCAN");
+
+ _outScanInfo(str, (Scan *) node);
+}
+
+static void
_outJoin(StringInfo str, Join *node)
{
WRITE_NODE_TYPE("JOIN");
@@ -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 1a83be65747..7fff94a8a39 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 265a5b369ee..7459acb30c0 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 05fe268a318..1d7e20c1e8b 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 */
@@ -538,6 +545,23 @@ set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
}
/*
+ * 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 5f5e9ec3721..fffa25dd844 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 $
*
*-------------------------------------------------------------------------
*/
@@ -775,6 +775,36 @@ cost_functionscan(Path *path, PlannerInfo *root, RelOptInfo *baserel)
}
/*
+ * 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
* the cost of reading the input data.
@@ -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 7ed718f0b31..ae51505954f 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 c02f6e195cd..42ae15cd483 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 2529c42ad95..11ef4765d86 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 0bc9a46c952..95e560478d9 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 dfc43149d3a..5570b33f485 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 631d6087d8e..2cc79ed239e 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 $
*
*-------------------------------------------------------------------------
*/
@@ -1083,6 +1083,25 @@ create_functionscan_path(PlannerInfo *root, RelOptInfo *rel)
}
/*
+ * 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
* relations.
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index bafe1b66731..f74faa5c977 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 8d06254a9f4..545b125197c 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 b086afe8ca2..4f7001b6f1a 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,20 +517,31 @@ 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
* SELECT. This can only happen if we are inside a CREATE RULE, and in
@@ -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,83 +678,221 @@ 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
+ else if (list_length(selectStmt->valuesLists) > 1)
{
/*
- * For INSERT ... VALUES, transform the given list of values to form a
- * targetlist for the INSERT.
+ * 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.
*/
- qry->targetList = transformTargetList(pstate, stmt->targetList);
+ 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
+ {
+ /*----------
+ * 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.
+ *----------
+ */
+ List *valuesLists = selectStmt->valuesLists;
- /*
- * Now we are done with SELECT-like processing, and can get on with
- * transforming the target list to match the INSERT target columns.
- */
+ Assert(list_length(valuesLists) == 1);
- /* 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;
+ /* Do basic expression transformation (same as a ROW() expr) */
+ exprList = transformExpressionList(pstate,
+ (List *) linitial(valuesLists));
- /* Validate stmt->cols list, or build default list if no list given */
- icolumns = checkInsertTargets(pstate, stmt->cols, &attrnos);
+ /* Prepare row for assignment to target table */
+ exprList = transformInsertRow(pstate, exprList,
+ stmt->cols,
+ icolumns, 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
* SQL92 allows constraints to be scattered all over, so thumb through
@@ -1934,6 +2102,187 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
}
/*
+ * 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 434f05bc4d8..4586c77f8a1 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 <node> select_no_parens select_with_parens select_clause
- simple_select
+ simple_select values_clause
%type <node> alter_column_default opclass_item alter_using
%type <ival> 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 <list> when_clause_list
%type <ival> sub_type
%type <list> OptCreateAs CreateAsList
-%type <node> CreateAsElement
+%type <node> CreateAsElement values_item
%type <value> NumericOnly FloatOnly IntegerOnly
%type <alias> alias_clause
%type <sortby> sortby
@@ -308,7 +308,7 @@ static void doNegateFloat(Value *v);
%type <jexpr> joined_table
%type <range> relation_expr
%type <range> relation_expr_opt_alias
-%type <target> target_el insert_target_el update_target_el insert_column_item
+%type <target> target_el update_target_el insert_column_item
%type <typnam> Typename SimpleTypename ConstTypename
GenericType Numeric opt_float
@@ -5342,40 +5342,23 @@ InsertStmt:
;
insert_rest:
- VALUES '(' insert_target_list ')'
- {
- $$ = makeNode(InsertStmt);
- $$->cols = NIL;
- $$->targetList = $3;
- $$->selectStmt = NULL;
- }
- | DEFAULT VALUES
- {
- $$ = makeNode(InsertStmt);
- $$->cols = NIL;
- $$->targetList = NIL;
- $$->selectStmt = NULL;
- }
- | SelectStmt
+ SelectStmt
{
$$ = makeNode(InsertStmt);
$$->cols = NIL;
- $$->targetList = NIL;
$$->selectStmt = $1;
}
- | '(' insert_column_list ')' VALUES '(' insert_target_list ')'
+ | '(' insert_column_list ')' SelectStmt
{
$$ = makeNode(InsertStmt);
$$->cols = $2;
- $$->targetList = $6;
- $$->selectStmt = NULL;
+ $$->selectStmt = $4;
}
- | '(' insert_column_list ')' SelectStmt
+ | DEFAULT VALUES
{
$$ = makeNode(InsertStmt);
- $$->cols = $2;
- $$->targetList = NIL;
- $$->selectStmt = $4;
+ $$->cols = NIL;
+ $$->selectStmt = NULL;
}
;
@@ -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 11ef7e4f1e9..6a7117e98bf 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 f1b6a45e7fc..9c1b58704e9 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 10f71712ff6..e9896be6349 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 $
*
*-------------------------------------------------------------------------
*/
@@ -941,6 +941,75 @@ addRangeTableEntryForFunction(ParseState *pstate,
}
/*
+ * 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).
*
* This is much like addRangeTableEntry() except that it makes a join RTE.
@@ -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 6f2e44f6e12..9258acccfbc 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);
@@ -152,6 +156,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
* table's OID and column number.
@@ -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 b6eac7417f8..a12aea6c38a 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 42c965f3c2a..286fc5b498c 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 a2254b6e481..8228fb26462 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 23b0ac9ce4f..8d93308fceb 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 00000000000..5a952bdd322
--- /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 8dec4130e28..2e981240037 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 53f3ee1d610..eb31fd2b6e7 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 5c227156f06..d0fa16ff51c 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 (<exprlist>), (<exprlist>), ... */
} RTEKind;
typedef struct RangeTblEntry
@@ -554,6 +555,11 @@ typedef struct RangeTblEntry
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):
*
* joinaliasvars is a list of Vars or COALESCE expressions corresponding
@@ -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
@@ -717,6 +720,16 @@ typedef struct SelectStmt
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 78a472342bd..5c2de28a417 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 27928b89a53..30388571119 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 71426b5706a..e6005094b78 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 33ebad8abce..9e2df63c54a 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 6080696baa0..bbd154efe36 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,