0001-Transform-CREATE-TABLE-AS-SELECT-INTO-into-a-utility.patch

text/x-patch

Filename: 0001-Transform-CREATE-TABLE-AS-SELECT-INTO-into-a-utility.patch
Type: text/x-patch
Part: 0
Message: Re: Command Triggers

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: format-patch
Series: patch 0001
Subject: Transform CREATE TABLE AS/SELECT INTO into a utility statement
File+
src/backend/commands/copy.c 14 6
src/backend/commands/prepare.c 7 62
src/backend/commands/tablecmds.c 332 0
src/backend/commands/view.c 16 4
src/backend/executor/execMain.c 25 389
src/backend/executor/execUtils.c 0 2
src/backend/executor/functions.c 1 3
src/backend/executor/spi.c 6 4
src/backend/nodes/copyfuncs.c 15 3
src/backend/nodes/equalfuncs.c 14 2
src/backend/nodes/outfuncs.c 1 3
src/backend/nodes/readfuncs.c 0 1
src/backend/optimizer/plan/planner.c 0 1
src/backend/optimizer/plan/subselect.c 0 1
src/backend/optimizer/prep/prepjointree.c 2 4
src/backend/optimizer/util/clauses.c 1 3
src/backend/parser/analyze.c 77 36
src/backend/parser/gram.y 19 9
src/backend/parser/parse_clause.c 15 6
src/backend/parser/parse_cte.c 14 7
src/backend/parser/parse_expr.c 15 6
src/backend/parser/parser.c 38 0
src/backend/rewrite/rewriteDefine.c 1 2
src/backend/tcop/pquery.c 4 9
src/backend/tcop/utility.c 21 25
src/include/access/htup.h 1 1
src/include/commands/prepare.h 3 0
src/include/commands/tablecmds.h 4 0
src/include/executor/executor.h 5 0
src/include/nodes/execnodes.h 7 2
src/include/nodes/nodes.h 1 0
src/include/nodes/parsenodes.h 15 5
src/include/nodes/plannodes.h 0 2
src/pl/plpgsql/src/pl_exec.c 8 19
src/test/regress/expected/select_into.out 19 0
src/test/regress/expected/transactions.out 1 1
src/test/regress/sql/select_into.sql 15 0
From d8fb1f9adbddd1eef123d66a89a9fc0ecd96f60b Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Sun, 4 Dec 2011 19:51:31 +0100
Subject: [PATCH] Transform CREATE TABLE AS/SELECT INTO into a utility
 statement

---
 src/backend/commands/copy.c                |   20 +-
 src/backend/commands/prepare.c             |   69 +----
 src/backend/commands/tablecmds.c           |  332 ++++++++++++++++++++++
 src/backend/commands/view.c                |   20 +-
 src/backend/executor/execMain.c            |  414 ++--------------------------
 src/backend/executor/execUtils.c           |    2 -
 src/backend/executor/functions.c           |    4 +-
 src/backend/executor/spi.c                 |   10 +-
 src/backend/nodes/copyfuncs.c              |   18 +-
 src/backend/nodes/equalfuncs.c             |   16 +-
 src/backend/nodes/outfuncs.c               |    4 +-
 src/backend/nodes/readfuncs.c              |    1 -
 src/backend/optimizer/plan/planner.c       |    1 -
 src/backend/optimizer/plan/subselect.c     |    1 -
 src/backend/optimizer/prep/prepjointree.c  |    6 +-
 src/backend/optimizer/util/clauses.c       |    4 +-
 src/backend/parser/analyze.c               |  113 +++++---
 src/backend/parser/gram.y                  |   28 ++-
 src/backend/parser/parse_clause.c          |   21 +-
 src/backend/parser/parse_cte.c             |   21 +-
 src/backend/parser/parse_expr.c            |   21 +-
 src/backend/parser/parser.c                |   38 +++
 src/backend/rewrite/rewriteDefine.c        |    3 +-
 src/backend/tcop/pquery.c                  |   13 +-
 src/backend/tcop/utility.c                 |   46 ++--
 src/include/access/htup.h                  |    2 +-
 src/include/commands/prepare.h             |    3 +
 src/include/commands/tablecmds.h           |    4 +
 src/include/executor/executor.h            |    5 +
 src/include/nodes/execnodes.h              |    9 +-
 src/include/nodes/nodes.h                  |    1 +
 src/include/nodes/parsenodes.h             |   20 +-
 src/include/nodes/plannodes.h              |    2 -
 src/pl/plpgsql/src/pl_exec.c               |   27 +--
 src/test/regress/expected/select_into.out  |   19 ++
 src/test/regress/expected/transactions.out |    2 +-
 src/test/regress/sql/select_into.sql       |   15 +
 37 files changed, 717 insertions(+), 618 deletions(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 9c994ef..6ace292 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1190,6 +1190,20 @@ BeginCopy(bool is_from,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("COPY (SELECT) WITH OIDS is not supported")));
 
+
+		/* Query mustn't use INTO, either */
+		if(IsA(raw_query, SelectStmt))
+		{
+			SelectStmt* first = (SelectStmt*)raw_query;
+			while (first && first->op != SETOP_NONE)
+				first = first->larg;
+
+			if (first->intoClause)
+				ereport(ERROR,
+				        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				         errmsg("COPY (SELECT INTO) is not supported")));
+		}
+
 		/*
 		 * Run parse analysis and rewrite.	Note this also acquires sufficient
 		 * locks on the source table(s).
@@ -1211,12 +1225,6 @@ BeginCopy(bool is_from,
 		Assert(query->commandType == CMD_SELECT);
 		Assert(query->utilityStmt == NULL);
 
-		/* Query mustn't use INTO, either */
-		if (query->intoClause)
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("COPY (SELECT INTO) is not supported")));
-
 		/* plan the query */
 		plan = planner(query, 0, NULL);
 
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index a949215..61a5f89 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -43,8 +43,6 @@
 static HTAB *prepared_queries = NULL;
 
 static void InitQueryHashTable(void);
-static ParamListInfo EvaluateParams(PreparedStatement *pstmt, List *params,
-			   const char *queryString, EState *estate);
 static Datum build_regtype_array(Oid *param_types, int num_params);
 
 /*
@@ -177,6 +175,9 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString)
  * EXECUTE, which we might need for error reporting while processing the
  * parameter expressions.  The query_string that we copy from the plan
  * source is that of the original PREPARE.
+ *
+ * If you add additional things here you should check whether
+ * CreateTableAs also needs them.
  */
 void
 ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
@@ -222,51 +223,9 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
 	query_string = MemoryContextStrdup(PortalGetHeapMemory(portal),
 									   entry->plansource->query_string);
 
-	/*
-	 * For CREATE TABLE / AS EXECUTE, we must make a copy of the stored query
-	 * so that we can modify its destination (yech, but this has always been
-	 * ugly).  For regular EXECUTE we can just use the cached query, since the
-	 * executor is read-only.
-	 */
-	if (stmt->into)
-	{
-		MemoryContext oldContext;
-		PlannedStmt *pstmt;
-
-		/* Replan if needed, and increment plan refcount transiently */
-		cplan = GetCachedPlan(entry->plansource, paramLI, true);
-
-		/* Copy plan into portal's context, and modify */
-		oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
-
-		plan_list = copyObject(cplan->stmt_list);
-
-		if (list_length(plan_list) != 1)
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("prepared statement is not a SELECT")));
-		pstmt = (PlannedStmt *) linitial(plan_list);
-		if (!IsA(pstmt, PlannedStmt) ||
-			pstmt->commandType != CMD_SELECT ||
-			pstmt->utilityStmt != NULL)
-			ereport(ERROR,
-					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-					 errmsg("prepared statement is not a SELECT")));
-		pstmt->intoClause = copyObject(stmt->into);
-
-		MemoryContextSwitchTo(oldContext);
-
-		/* We no longer need the cached plan refcount ... */
-		ReleaseCachedPlan(cplan, true);
-		/* ... and we don't want the portal to depend on it, either */
-		cplan = NULL;
-	}
-	else
-	{
-		/* Replan if needed, and increment plan refcount for portal */
-		cplan = GetCachedPlan(entry->plansource, paramLI, false);
-		plan_list = cplan->stmt_list;
-	}
+	/* Replan if needed, and increment plan refcount for portal */
+	cplan = GetCachedPlan(entry->plansource, paramLI, false);
+	plan_list = cplan->stmt_list;
 
 	PortalDefineQuery(portal,
 					  NULL,
@@ -302,7 +261,7 @@ ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
  * CreateQueryDesc(), which allows the executor to make use of the parameters
  * during query execution.
  */
-static ParamListInfo
+ParamListInfo
 EvaluateParams(PreparedStatement *pstmt, List *params,
 			   const char *queryString, EState *estate)
 {
@@ -666,20 +625,6 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es,
 
 		if (IsA(pstmt, PlannedStmt))
 		{
-			if (execstmt->into)
-			{
-				if (pstmt->commandType != CMD_SELECT ||
-					pstmt->utilityStmt != NULL)
-					ereport(ERROR,
-							(errcode(ERRCODE_WRONG_OBJECT_TYPE),
-							 errmsg("prepared statement is not a SELECT")));
-
-				/* Copy the stmt so we can modify it */
-				pstmt = copyObject(pstmt);
-
-				pstmt->intoClause = execstmt->into;
-			}
-
 			ExplainOnePlan(pstmt, es, query_string, paramLI);
 		}
 		else
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index c4622c0..d530cd5 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -44,6 +44,7 @@
 #include "commands/cluster.h"
 #include "commands/comment.h"
 #include "commands/defrem.h"
+#include "commands/prepare.h"
 #include "commands/sequence.h"
 #include "commands/tablecmds.h"
 #include "commands/tablespace.h"
@@ -68,6 +69,7 @@
 #include "parser/parser.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteHandler.h"
+#include "tcop/tcopprot.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
 #include "storage/lock.h"
@@ -195,6 +197,18 @@ struct dropmsgstrings
 	const char *drophint_msg;
 };
 
+/*
+ * Support structure for CTAS
+ */
+typedef struct
+{
+	DestReceiver pub;			/* publicly-known function pointers */
+	EState	   *estate;			/* EState we are working with */
+	Relation	rel;			/* Relation to write to */
+	int			hi_options;		/* heap_insert performance options */
+	BulkInsertState bistate;	/* bulk insert state */
+} DR_intorel;
+
 static const struct dropmsgstrings dropmsgstringarray[] = {
 	{RELKIND_RELATION,
 		ERRCODE_UNDEFINED_TABLE,
@@ -640,6 +654,235 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
 	return relationId;
 }
 
+void
+CreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
+              ParamListInfo params, DestReceiver *dest){
+	IntoClause *into = stmt->into;
+	Query *query;
+	List *rewritten;
+	PlannedStmt *plan;
+	QueryDesc *queryDesc;
+	Relation intoRelationDesc;
+	CreateStmt *create;
+	int natt;
+	Oid intoRelationId;
+	ListCell *lc;
+	int eflags = 0;
+	EState *param_estate = NULL;
+	CachedPlan *cplan = NULL;
+
+	Assert(IsA(stmt->query, Query));
+	query = (Query*)stmt->query;
+
+	/*
+	 * Construct plans for the supported
+	 */
+	if(query->commandType == CMD_SELECT){
+		rewritten = QueryRewrite((Query *) copyObject(stmt->query));
+
+		if(!rewritten)
+			elog(ERROR, "CREATE TABLE AS/SELECT INTO query rewrote to nothing");
+
+		if(list_length(rewritten) != 1)
+			elog(ERROR, "CREATE TABLE AS/SELECT INTO query rewrote to more than one query");
+
+		plan = pg_plan_query(lfirst(list_head(rewritten)), 0, params);
+	}
+	else if(query->commandType == CMD_UTILITY &&
+	        IsA(query->utilityStmt, ExecuteStmt)){
+		/*
+		 * We cannot easily share more infrastructure with prepare.c's
+		 * ExecuteQuery because teaching it to return a started
+		 * executor would amount to about the same amount of
+		 * duplication as doing it here.
+		 *
+		 */
+		ExecuteStmt *stmt;
+		PreparedStatement *entry;
+		List *plan_list;
+		ParamListInfo paramLI = NULL;
+
+		Assert(IsA(query->utilityStmt, ExecuteStmt));
+		stmt = (ExecuteStmt*)(query->utilityStmt);
+
+		entry = FetchPreparedStatement(stmt->name, true);
+
+		/* Shouldn't find a non-fixed-result cached plan */
+		if (!entry->plansource->fixed_result)
+			elog(ERROR, "EXECUTE does not support variable-result cached plans");
+
+		/* Evaluate parameters, if any */
+		if (entry->plansource->num_params > 0)
+		{
+			param_estate = CreateExecutorState();
+			param_estate->es_param_list_info = params;
+			paramLI = EvaluateParams(entry, stmt->params,
+			                         queryString, param_estate);
+
+		}
+
+		cplan = GetCachedPlan(entry->plansource, paramLI, true);
+		plan_list = cplan->stmt_list;
+
+		if (list_length(plan_list) != 1)
+			ereport(ERROR,
+					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+					 errmsg("prepared statement is not a SELECT")));
+
+		plan = (PlannedStmt *) linitial(plan_list);
+		if (!IsA(plan, PlannedStmt) ||
+			plan->commandType != CMD_SELECT ||
+			plan->utilityStmt != NULL)
+			ereport(ERROR,
+			        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+			         errmsg("prepared statement is not a SELECT")));
+
+		queryString = pstrdup(entry->plansource->query_string);
+	}
+	else{
+		elog(ERROR, "unsupported command for CREATE TABLE AS");
+		abort();
+	}
+
+
+	/*
+	 * Use a snapshot with an updated command ID to ensure this query sees
+	 * results of any previously executed queries.
+	 */
+	PushCopiedSnapshot(GetActiveSnapshot());
+	UpdateActiveSnapshotCommandId();
+
+	queryDesc = CreateQueryDesc(plan, queryString,
+								GetActiveSnapshot(), InvalidSnapshot,
+								CreateDestReceiver(DestIntoRel), params,
+	                            false);
+
+	/*
+	 * We need to tell the executor whether it has to produce oids or
+	 * not because it doesn't have enough information to do so itself
+	 * as were not telling it that its inserting into a table (because
+	 * that would be too complicated for now)
+	 */
+	if(interpretOidsOption(into->options))
+		eflags |= EXEC_FLAG_FORCE_OIDS_ON;
+	else
+		eflags |= EXEC_FLAG_FORCE_OIDS_OFF;
+
+	/*
+	 * call ExecutorStart to prepare the plan for execution. Only
+	 * after this we have enough information to actually create a
+	 * target relation
+	 */
+	ExecutorStart(queryDesc, eflags);
+
+	/*
+	 * create the target relation
+	 */
+	create = makeNode(CreateStmt);
+	create->relation = into->rel;
+	create->options = into->options;
+	create->oncommit = into->onCommit;
+	create->tablespacename = into->tableSpaceName;
+
+	/*
+	 * If a column name list was specified in CREATE TABLE AS,
+	 * override the column names derived from the query.  (Too few
+	 * column names are OK, too many are not.)
+	 */
+	if (list_length(into->colNames) > queryDesc->tupDesc->natts)
+		ereport(ERROR,
+		        (errcode(ERRCODE_SYNTAX_ERROR),
+		         errmsg("CREATE TABLE AS specifies too many column names")));
+	lc = list_head(into->colNames);
+	for(natt = 0; natt < queryDesc->tupDesc->natts; natt++){
+		ColumnDef *col = makeNode(ColumnDef);
+		TypeName *type = makeNode(TypeName);
+
+		Form_pg_attribute attribute = queryDesc->tupDesc->attrs[natt];
+
+		if(!lc)
+			col->colname = NameStr(attribute->attname);
+		else{
+			col->colname = strVal(lfirst(lc));
+			lc = lnext(lc);
+		}
+
+		col->collOid = attribute->attcollation;
+
+		type->typeOid = attribute->atttypid;
+		type->typemod = attribute->atttypmod;
+		col->typeName= type;
+
+		/*
+		 * We check the column type here because collOid=InvalidOid
+		 * denotes an invalid oid for a collatable type in
+		 * queryDesc->tupDesc but GetColumnDefCollation - which will
+		 * be called by DefineRelation - defers the default collation
+		 * out of that...
+		 * XXX: A better solution would be welcome.
+		 */
+		CheckAttributeType(col->colname, col->typeName->typeOid, col->collOid,
+		                   NULL, false);
+		create->tableElts = lappend(create->tableElts, col);
+	}
+
+	/*
+	 * Actually create the target table
+	 */
+	intoRelationId = DefineRelation(create, RELKIND_RELATION, InvalidOid);
+	intoRelationDesc = heap_open(intoRelationId, AccessExclusiveLock);
+
+
+	if(!into->skipData){
+		DR_intorel *myState = (DR_intorel *) queryDesc->dest;
+		/* setup the target of the DestIntoRel receiver */
+		myState->estate = queryDesc->estate;
+		myState->rel = intoRelationDesc;
+
+		/* run the plan */
+		ExecutorRun(queryDesc, ForwardScanDirection, 0L);
+
+	}
+
+	/*
+	 * Check INSERT permission on the constructed table.
+	 *
+	 * XXX: It would arguable make sense to do this check only if
+	 * into->skipData is true.
+	 */
+	{
+		RangeTblEntry *rte = makeNode(RangeTblEntry);
+		rte->rtekind = RTE_RELATION;
+		rte->relid = intoRelationId;
+		rte->relkind = RELKIND_RELATION;
+		rte->requiredPerms = ACL_INSERT;
+
+		for (natt = 1; natt <= intoRelationDesc->rd_att->natts; natt++)
+			rte->modifiedCols = bms_add_member(rte->modifiedCols,
+			                                   natt - FirstLowInvalidHeapAttributeNumber);
+
+		ExecCheckRTPerms(list_make1(rte), true);
+	}
+
+	/* run cleanup too */
+	ExecutorFinish(queryDesc);
+	ExecutorEnd(queryDesc);
+
+	/* close rel, but keep lock until commit */
+	heap_close(intoRelationDesc, NoLock);
+
+	FreeQueryDesc(queryDesc);
+
+	if(cplan){
+		ReleaseCachedPlan(cplan, true);
+
+		if (param_estate)
+			FreeExecutorState(param_estate);
+	}
+
+	PopActiveSnapshot();
+}
+
 /*
  * Emit the right error or warning message for a "DROP" command issued on a
  * non-existent relation
@@ -9771,3 +10014,92 @@ AtEOSubXact_on_commit_actions(bool isCommit, SubTransactionId mySubid,
 		}
 	}
 }
+
+/*
+ * Support for SELECT INTO (a/k/a CREATE TABLE AS)
+ *
+ * We implement SELECT INTO by diverting SELECT's normal output with
+ * a specialized DestReceiver type.
+ */
+
+/*
+ * intorel_startup --- executor startup
+ */
+static void
+intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
+{
+	DR_intorel *myState = (DR_intorel *) self;
+	myState->bistate = GetBulkInsertState();
+}
+
+/*
+ * intorel_receive --- receive one tuple
+ */
+static void
+intorel_receive(TupleTableSlot *slot, DestReceiver *self)
+{
+	DR_intorel *myState = (DR_intorel *) self;
+	HeapTuple	tuple;
+
+	/*
+	 * get the heap tuple out of the tuple table slot, making sure we have a
+	 * writable copy
+	 */
+	tuple = ExecMaterializeSlot(slot);
+
+	/*
+	 * force assignment of new OID (see comments in ExecInsert)
+	 */
+	if (myState->rel->rd_rel->relhasoids)
+		HeapTupleSetOid(tuple, InvalidOid);
+
+	heap_insert(myState->rel,
+				tuple,
+				myState->estate->es_output_cid,
+				myState->hi_options,
+				myState->bistate);
+
+	/* We know this is a newly created relation, so there are no indexes */
+}
+
+/*
+ * intorel_shutdown --- executor end
+ */
+static void
+intorel_shutdown(DestReceiver *self)
+{
+	DR_intorel *myState = (DR_intorel *) self;
+
+	FreeBulkInsertState(myState->bistate);
+	if (myState->hi_options & HEAP_INSERT_SKIP_WAL)
+		heap_sync(myState->rel);
+}
+
+/*
+ * intorel_destroy --- release DestReceiver object
+ */
+static void
+intorel_destroy(DestReceiver *self)
+{
+	pfree(self);
+}
+
+/*
+ * CreateIntoRelDestReceiver -- create a suitable DestReceiver object
+ *
+ */
+DestReceiver *
+CreateIntoRelDestReceiver(void)
+{
+	DR_intorel *self = (DR_intorel *) palloc0(sizeof(DR_intorel));
+
+	self->pub.receiveSlot = intorel_receive;
+	self->pub.rStartup = intorel_startup;
+	self->pub.rShutdown = intorel_shutdown;
+	self->pub.rDestroy = intorel_destroy;
+	self->pub.mydest = DestIntoRel;
+
+	/* private fields will be set by OpenIntoRel */
+
+	return (DestReceiver *) self;
+}
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index b238199..5977092 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -420,6 +420,22 @@ DefineView(ViewStmt *stmt, const char *queryString)
 	RangeVar   *view;
 
 	/*
+	 * We check for SELECT INTO before parse analysis because that
+	 * would complain about the SELECT INTO without specific enough detail.
+	 */
+	if(IsA(stmt->query, SelectStmt))
+	{
+		SelectStmt* first = (SelectStmt*)stmt->query;
+		while (first && first->op != SETOP_NONE)
+			first = first->larg;
+
+		if (first->intoClause)
+			ereport(ERROR,
+			        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			         errmsg("views must not contain SELECT INTO")));
+	}
+
+	/*
 	 * Run parse analysis to convert the raw parse tree to a Query.  Note this
 	 * also acquires sufficient locks on the source table(s).
 	 *
@@ -441,10 +457,6 @@ DefineView(ViewStmt *stmt, const char *queryString)
 	 * DefineQueryRewrite(), but that function will complain about a bogus ON
 	 * SELECT rule, and we'd rather the message complain about a view.
 	 */
-	if (viewParse->intoClause != NULL)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("views must not contain SELECT INTO")));
 	if (viewParse->hasModifyingCTE)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index d19e097..6b2cf4c 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -87,12 +87,6 @@ static bool ExecCheckRTEPerms(RangeTblEntry *rte);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
 static void EvalPlanQualStart(EPQState *epqstate, EState *parentestate,
 				  Plan *planTree);
-static void OpenIntoRel(QueryDesc *queryDesc);
-static void CloseIntoRel(QueryDesc *queryDesc);
-static void intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo);
-static void intorel_receive(TupleTableSlot *slot, DestReceiver *self);
-static void intorel_shutdown(DestReceiver *self);
-static void intorel_destroy(DestReceiver *self);
 
 /* end of local decls */
 
@@ -171,11 +165,10 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 		case CMD_SELECT:
 
 			/*
-			 * SELECT INTO, SELECT FOR UPDATE/SHARE and modifying CTEs need to
+			 * SELECT FOR UPDATE/SHARE and modifying CTEs need to
 			 * mark tuples
 			 */
-			if (queryDesc->plannedstmt->intoClause != NULL ||
-				queryDesc->plannedstmt->rowMarks != NIL ||
+			if (queryDesc->plannedstmt->rowMarks != NIL ||
 				queryDesc->plannedstmt->hasModifyingCTE)
 				estate->es_output_cid = GetCurrentCommandId(true);
 
@@ -209,6 +202,13 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags)
 	estate->es_top_eflags = eflags;
 	estate->es_instrument = queryDesc->instrument_options;
 
+	if(eflags & EXEC_FLAG_FORCE_OIDS_ON)
+		estate->es_force_oids = ESTATE_FORCE_OIDS_ON;
+	else if(eflags & EXEC_FLAG_FORCE_OIDS_OFF)
+		estate->es_force_oids = ESTATE_FORCE_OIDS_OFF;
+	else
+		estate->es_force_oids = ESTATE_FORCE_OIDS_DEFAULT;
+
 	/*
 	 * Initialize the plan state tree
 	 */
@@ -307,13 +307,6 @@ standard_ExecutorRun(QueryDesc *queryDesc,
 		(*dest->rStartup) (dest, operation, queryDesc->tupDesc);
 
 	/*
-	 * if it's CREATE TABLE AS ... WITH NO DATA, skip plan execution
-	 */
-	if (estate->es_select_into &&
-		queryDesc->plannedstmt->intoClause->skipData)
-		direction = NoMovementScanDirection;
-
-	/*
 	 * run plan
 	 */
 	if (!ScanDirectionIsNoMovement(direction))
@@ -448,12 +441,6 @@ standard_ExecutorEnd(QueryDesc *queryDesc)
 
 	ExecEndPlan(queryDesc->planstate, estate);
 
-	/*
-	 * Close the SELECT INTO relation if any
-	 */
-	if (estate->es_select_into)
-		CloseIntoRel(queryDesc);
-
 	/* do away with our snapshots */
 	UnregisterSnapshot(estate->es_snapshot);
 	UnregisterSnapshot(estate->es_crosscheck_snapshot);
@@ -703,15 +690,6 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
 {
 	ListCell   *l;
 
-	/*
-	 * CREATE TABLE AS or SELECT INTO?
-	 *
-	 * XXX should we allow this if the destination is temp?  Considering that
-	 * it would still require catalog changes, probably not.
-	 */
-	if (plannedstmt->intoClause != NULL)
-		PreventCommandIfReadOnly(CreateCommandTag((Node *) plannedstmt));
-
 	/* Fail if write permissions are requested on any non-temp table */
 	foreach(l, plannedstmt->rtable)
 	{
@@ -861,18 +839,6 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	}
 
 	/*
-	 * Detect whether we're doing SELECT INTO.  If so, set the es_into_oids
-	 * flag appropriately so that the plan tree will be initialized with the
-	 * correct tuple descriptors.  (Other SELECT INTO stuff comes later.)
-	 */
-	estate->es_select_into = false;
-	if (operation == CMD_SELECT && plannedstmt->intoClause != NULL)
-	{
-		estate->es_select_into = true;
-		estate->es_into_oids = interpretOidsOption(plannedstmt->intoClause->options);
-	}
-
-	/*
 	 * Initialize the executor's tuple table to empty.
 	 */
 	estate->es_tupleTable = NIL;
@@ -923,9 +889,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 	planstate = ExecInitNode(plan, estate, eflags);
 
 	/*
-	 * Get the tuple descriptor describing the type of tuples to return. (this
-	 * is especially important if we are creating a relation with "SELECT
-	 * INTO")
+	 * Get the tuple descriptor describing the type of tuples to return.
 	 */
 	tupType = ExecGetResultType(planstate);
 
@@ -965,16 +929,6 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 
 	queryDesc->tupDesc = tupType;
 	queryDesc->planstate = planstate;
-
-	/*
-	 * If doing SELECT INTO, initialize the "into" relation.  We must wait
-	 * till now so we have the "clean" result tuple type to create the new
-	 * table from.
-	 *
-	 * If EXPLAIN, skip creating the "into" relation.
-	 */
-	if (estate->es_select_into && !(eflags & EXEC_FLAG_EXPLAIN_ONLY))
-		OpenIntoRel(queryDesc);
 }
 
 /*
@@ -1227,7 +1181,7 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
 /*
  *		ExecContextForcesOids
  *
- * This is pretty grotty: when doing INSERT, UPDATE, or SELECT INTO,
+ * This is pretty grotty: when doing INSERT or UPDATE
  * we need to ensure that result tuples have space for an OID iff they are
  * going to be stored into a relation that has OIDs.  In other contexts
  * we are free to choose whether to leave space for OIDs in result tuples
@@ -1252,15 +1206,25 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
  * the ModifyTable node, so ModifyTable has to set es_result_relation_info
  * while initializing each subplan.
  *
- * SELECT INTO is even uglier, because we don't have the INTO relation's
- * descriptor available when this code runs; we have to look aside at a
- * flag set by InitPlan().
+ * SELECT INTO is even uglier, because we don't have the INTO
+ * relation's descriptor available - because its only defined after
+ * the query started - when this code runs; we have to look aside at
+ * flags which is passed to ExecutorStart via eflags.
  */
 bool
 ExecContextForcesOids(PlanState *planstate, bool *hasoids)
 {
 	ResultRelInfo *ri = planstate->state->es_result_relation_info;
 
+	if(planstate->state->es_force_oids == ESTATE_FORCE_OIDS_ON){
+		*hasoids = true;
+		return true;
+	}
+	else if(planstate->state->es_force_oids == ESTATE_FORCE_OIDS_OFF){
+		*hasoids = false;
+		return true;
+	}
+
 	if (ri != NULL)
 	{
 		Relation	rel = ri->ri_RelationDesc;
@@ -1272,12 +1236,6 @@ ExecContextForcesOids(PlanState *planstate, bool *hasoids)
 		}
 	}
 
-	if (planstate->state->es_select_into)
-	{
-		*hasoids = planstate->state->es_into_oids;
-		return true;
-	}
-
 	return false;
 }
 
@@ -2224,8 +2182,7 @@ EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree)
 	estate->es_rowMarks = parentestate->es_rowMarks;
 	estate->es_top_eflags = parentestate->es_top_eflags;
 	estate->es_instrument = parentestate->es_instrument;
-	estate->es_select_into = parentestate->es_select_into;
-	estate->es_into_oids = parentestate->es_into_oids;
+
 	/* es_auxmodifytables must NOT be copied */
 
 	/*
@@ -2366,324 +2323,3 @@ EvalPlanQualEnd(EPQState *epqstate)
 	epqstate->planstate = NULL;
 	epqstate->origslot = NULL;
 }
-
-
-/*
- * Support for SELECT INTO (a/k/a CREATE TABLE AS)
- *
- * We implement SELECT INTO by diverting SELECT's normal output with
- * a specialized DestReceiver type.
- */
-
-typedef struct
-{
-	DestReceiver pub;			/* publicly-known function pointers */
-	EState	   *estate;			/* EState we are working with */
-	Relation	rel;			/* Relation to write to */
-	int			hi_options;		/* heap_insert performance options */
-	BulkInsertState bistate;	/* bulk insert state */
-} DR_intorel;
-
-/*
- * OpenIntoRel --- actually create the SELECT INTO target relation
- *
- * This also replaces QueryDesc->dest with the special DestReceiver for
- * SELECT INTO.  We assume that the correct result tuple type has already
- * been placed in queryDesc->tupDesc.
- */
-static void
-OpenIntoRel(QueryDesc *queryDesc)
-{
-	IntoClause *into = queryDesc->plannedstmt->intoClause;
-	EState	   *estate = queryDesc->estate;
-	TupleDesc	intoTupDesc = queryDesc->tupDesc;
-	Relation	intoRelationDesc;
-	char	   *intoName;
-	Oid			namespaceId;
-	Oid			tablespaceId;
-	Datum		reloptions;
-	Oid			intoRelationId;
-	DR_intorel *myState;
-	RangeTblEntry  *rte;
-	AttrNumber		attnum;
-	static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
-
-	Assert(into);
-
-	/*
-	 * XXX This code needs to be kept in sync with DefineRelation(). Maybe we
-	 * should try to use that function instead.
-	 */
-
-	/*
-	 * Check consistency of arguments
-	 */
-	if (into->onCommit != ONCOMMIT_NOOP
-		&& into->rel->relpersistence != RELPERSISTENCE_TEMP)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-				 errmsg("ON COMMIT can only be used on temporary tables")));
-
-	/*
-	 * If a column name list was specified in CREATE TABLE AS, override the
-	 * column names derived from the query.  (Too few column names are OK, too
-	 * many are not.)  It would probably be all right to scribble directly on
-	 * the query's result tupdesc, but let's be safe and make a copy.
-	 */
-	if (into->colNames)
-	{
-		ListCell   *lc;
-
-		intoTupDesc = CreateTupleDescCopy(intoTupDesc);
-		attnum = 1;
-		foreach(lc, into->colNames)
-		{
-			char	   *colname = strVal(lfirst(lc));
-
-			if (attnum > intoTupDesc->natts)
-				ereport(ERROR,
-						(errcode(ERRCODE_SYNTAX_ERROR),
-						 errmsg("CREATE TABLE AS specifies too many column names")));
-			namestrcpy(&(intoTupDesc->attrs[attnum - 1]->attname), colname);
-			attnum++;
-		}
-	}
-
-	/*
-	 * Find namespace to create in, check its permissions
-	 */
-	intoName = into->rel->relname;
-	namespaceId = RangeVarGetAndCheckCreationNamespace(into->rel);
-	RangeVarAdjustRelationPersistence(into->rel, namespaceId);
-
-	/*
-	 * Security check: disallow creating temp tables from security-restricted
-	 * code.  This is needed because calling code might not expect untrusted
-	 * tables to appear in pg_temp at the front of its search path.
-	 */
-	if (into->rel->relpersistence == RELPERSISTENCE_TEMP
-		&& InSecurityRestrictedOperation())
-		ereport(ERROR,
-				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-				 errmsg("cannot create temporary table within security-restricted operation")));
-
-	/*
-	 * Select tablespace to use.  If not specified, use default tablespace
-	 * (which may in turn default to database's default).
-	 */
-	if (into->tableSpaceName)
-	{
-		tablespaceId = get_tablespace_oid(into->tableSpaceName, false);
-	}
-	else
-	{
-		tablespaceId = GetDefaultTablespace(into->rel->relpersistence);
-		/* note InvalidOid is OK in this case */
-	}
-
-	/* Check permissions except when using the database's default space */
-	if (OidIsValid(tablespaceId) && tablespaceId != MyDatabaseTableSpace)
-	{
-		AclResult	aclresult;
-
-		aclresult = pg_tablespace_aclcheck(tablespaceId, GetUserId(),
-										   ACL_CREATE);
-
-		if (aclresult != ACLCHECK_OK)
-			aclcheck_error(aclresult, ACL_KIND_TABLESPACE,
-						   get_tablespace_name(tablespaceId));
-	}
-
-	/* Parse and validate any reloptions */
-	reloptions = transformRelOptions((Datum) 0,
-									 into->options,
-									 NULL,
-									 validnsps,
-									 true,
-									 false);
-	(void) heap_reloptions(RELKIND_RELATION, reloptions, true);
-
-	/* Now we can actually create the new relation */
-	intoRelationId = heap_create_with_catalog(intoName,
-											  namespaceId,
-											  tablespaceId,
-											  InvalidOid,
-											  InvalidOid,
-											  InvalidOid,
-											  GetUserId(),
-											  intoTupDesc,
-											  NIL,
-											  RELKIND_RELATION,
-											  into->rel->relpersistence,
-											  false,
-											  false,
-											  true,
-											  0,
-											  into->onCommit,
-											  reloptions,
-											  true,
-											  allowSystemTableMods);
-	Assert(intoRelationId != InvalidOid);
-
-	/*
-	 * Advance command counter so that the newly-created relation's catalog
-	 * tuples will be visible to heap_open.
-	 */
-	CommandCounterIncrement();
-
-	/*
-	 * If necessary, create a TOAST table for the INTO relation. Note that
-	 * AlterTableCreateToastTable ends with CommandCounterIncrement(), so that
-	 * the TOAST table will be visible for insertion.
-	 */
-	reloptions = transformRelOptions((Datum) 0,
-									 into->options,
-									 "toast",
-									 validnsps,
-									 true,
-									 false);
-
-	(void) heap_reloptions(RELKIND_TOASTVALUE, reloptions, true);
-
-	AlterTableCreateToastTable(intoRelationId, reloptions);
-
-	/*
-	 * And open the constructed table for writing.
-	 */
-	intoRelationDesc = heap_open(intoRelationId, AccessExclusiveLock);
-
-	/*
-	 * Check INSERT permission on the constructed table.
-	 */
-	rte = makeNode(RangeTblEntry);
-	rte->rtekind = RTE_RELATION;
-	rte->relid = intoRelationId;
-	rte->relkind = RELKIND_RELATION;
-	rte->requiredPerms = ACL_INSERT;
-
-	for (attnum = 1; attnum <= intoTupDesc->natts; attnum++)
-		rte->modifiedCols = bms_add_member(rte->modifiedCols,
-				attnum - FirstLowInvalidHeapAttributeNumber);
-
-	ExecCheckRTPerms(list_make1(rte), true);
-
-	/*
-	 * Now replace the query's DestReceiver with one for SELECT INTO
-	 */
-	queryDesc->dest = CreateDestReceiver(DestIntoRel);
-	myState = (DR_intorel *) queryDesc->dest;
-	Assert(myState->pub.mydest == DestIntoRel);
-	myState->estate = estate;
-	myState->rel = intoRelationDesc;
-
-	/*
-	 * We can skip WAL-logging the insertions, unless PITR or streaming
-	 * replication is in use. We can skip the FSM in any case.
-	 */
-	myState->hi_options = HEAP_INSERT_SKIP_FSM |
-		(XLogIsNeeded() ? 0 : HEAP_INSERT_SKIP_WAL);
-	myState->bistate = GetBulkInsertState();
-
-	/* Not using WAL requires smgr_targblock be initially invalid */
-	Assert(RelationGetTargetBlock(intoRelationDesc) == InvalidBlockNumber);
-}
-
-/*
- * CloseIntoRel --- clean up SELECT INTO at ExecutorEnd time
- */
-static void
-CloseIntoRel(QueryDesc *queryDesc)
-{
-	DR_intorel *myState = (DR_intorel *) queryDesc->dest;
-
-	/* OpenIntoRel might never have gotten called */
-	if (myState && myState->pub.mydest == DestIntoRel && myState->rel)
-	{
-		FreeBulkInsertState(myState->bistate);
-
-		/* If we skipped using WAL, must heap_sync before commit */
-		if (myState->hi_options & HEAP_INSERT_SKIP_WAL)
-			heap_sync(myState->rel);
-
-		/* close rel, but keep lock until commit */
-		heap_close(myState->rel, NoLock);
-
-		myState->rel = NULL;
-	}
-}
-
-/*
- * CreateIntoRelDestReceiver -- create a suitable DestReceiver object
- */
-DestReceiver *
-CreateIntoRelDestReceiver(void)
-{
-	DR_intorel *self = (DR_intorel *) palloc0(sizeof(DR_intorel));
-
-	self->pub.receiveSlot = intorel_receive;
-	self->pub.rStartup = intorel_startup;
-	self->pub.rShutdown = intorel_shutdown;
-	self->pub.rDestroy = intorel_destroy;
-	self->pub.mydest = DestIntoRel;
-
-	/* private fields will be set by OpenIntoRel */
-
-	return (DestReceiver *) self;
-}
-
-/*
- * intorel_startup --- executor startup
- */
-static void
-intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
-{
-	/* no-op */
-}
-
-/*
- * intorel_receive --- receive one tuple
- */
-static void
-intorel_receive(TupleTableSlot *slot, DestReceiver *self)
-{
-	DR_intorel *myState = (DR_intorel *) self;
-	HeapTuple	tuple;
-
-	/*
-	 * get the heap tuple out of the tuple table slot, making sure we have a
-	 * writable copy
-	 */
-	tuple = ExecMaterializeSlot(slot);
-
-	/*
-	 * force assignment of new OID (see comments in ExecInsert)
-	 */
-	if (myState->rel->rd_rel->relhasoids)
-		HeapTupleSetOid(tuple, InvalidOid);
-
-	heap_insert(myState->rel,
-				tuple,
-				myState->estate->es_output_cid,
-				myState->hi_options,
-				myState->bistate);
-
-	/* We know this is a newly created relation, so there are no indexes */
-}
-
-/*
- * intorel_shutdown --- executor end
- */
-static void
-intorel_shutdown(DestReceiver *self)
-{
-	/* no-op */
-}
-
-/*
- * intorel_destroy --- release DestReceiver object
- */
-static void
-intorel_destroy(DestReceiver *self)
-{
-	pfree(self);
-}
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 65591e2..acab607 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -137,8 +137,6 @@ CreateExecutorState(void)
 
 	estate->es_top_eflags = 0;
 	estate->es_instrument = 0;
-	estate->es_select_into = false;
-	estate->es_into_oids = false;
 	estate->es_finished = false;
 
 	estate->es_exprcontexts = NIL;
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 45ca5ec..30185f2 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -349,7 +349,6 @@ init_execution_state(List *queryTree_list,
 
 			if (ps->commandType == CMD_SELECT &&
 				ps->utilityStmt == NULL &&
-				ps->intoClause == NULL &&
 				!ps->hasModifyingCTE)
 				fcache->lazyEval = lasttages->lazyEval = true;
 		}
@@ -1307,8 +1306,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
 	 */
 	if (parse &&
 		parse->commandType == CMD_SELECT &&
-		parse->utilityStmt == NULL &&
-		parse->intoClause == NULL)
+		parse->utilityStmt == NULL)
 	{
 		tlist_ptr = &parse->targetList;
 		tlist = parse->targetList;
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 688279c..d9f08ef 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -1921,7 +1921,11 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
 				if (_SPI_current->tuptable)
 					_SPI_current->processed = _SPI_current->tuptable->alloced -
 						_SPI_current->tuptable->free;
-				res = SPI_OK_UTILITY;
+				if(nodeTag(stmt) == T_CreateTableAsStmt &&
+				   ((CreateTableAsStmt*)stmt)->is_select_into)
+					res = SPI_OK_SELINTO;
+				else
+					res = SPI_OK_UTILITY;
 			}
 
 			/*
@@ -2046,9 +2050,7 @@ _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, long tcount)
 	{
 		case CMD_SELECT:
 			Assert(queryDesc->plannedstmt->utilityStmt == NULL);
-			if (queryDesc->plannedstmt->intoClause)		/* select into table? */
-				res = SPI_OK_SELINTO;
-			else if (queryDesc->dest->mydest != DestSPI)
+			if (queryDesc->dest->mydest != DestSPI)
 			{
 				/* Don't return SPI_OK_SELECT if we're discarding result */
 				res = SPI_OK_UTILITY;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index c70a5bd..d8e7eb3 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -85,7 +85,6 @@ _copyPlannedStmt(PlannedStmt *from)
 	COPY_NODE_FIELD(rtable);
 	COPY_NODE_FIELD(resultRelations);
 	COPY_NODE_FIELD(utilityStmt);
-	COPY_NODE_FIELD(intoClause);
 	COPY_NODE_FIELD(subplans);
 	COPY_BITMAPSET_FIELD(rewindPlanIDs);
 	COPY_NODE_FIELD(rowMarks);
@@ -2417,7 +2416,6 @@ _copyQuery(Query *from)
 	COPY_SCALAR_FIELD(canSetTag);
 	COPY_NODE_FIELD(utilityStmt);
 	COPY_SCALAR_FIELD(resultRelation);
-	COPY_NODE_FIELD(intoClause);
 	COPY_SCALAR_FIELD(hasAggs);
 	COPY_SCALAR_FIELD(hasWindowFuncs);
 	COPY_SCALAR_FIELD(hasSubLinks);
@@ -3201,6 +3199,18 @@ _copyExplainStmt(ExplainStmt *from)
 	return newnode;
 }
 
+static CreateTableAsStmt *
+_copyCreateTableAsStmt(CreateTableAsStmt *from)
+{
+	CreateTableAsStmt *newnode = makeNode(CreateTableAsStmt);
+
+	COPY_NODE_FIELD(query);
+	COPY_NODE_FIELD(into);
+	COPY_SCALAR_FIELD(is_select_into);
+
+	return newnode;
+}
+
 static CreateSeqStmt *
 _copyCreateSeqStmt(CreateSeqStmt *from)
 {
@@ -3608,7 +3618,6 @@ _copyExecuteStmt(ExecuteStmt *from)
 	ExecuteStmt *newnode = makeNode(ExecuteStmt);
 
 	COPY_STRING_FIELD(name);
-	COPY_NODE_FIELD(into);
 	COPY_NODE_FIELD(params);
 
 	return newnode;
@@ -4243,6 +4252,9 @@ copyObject(void *from)
 		case T_ExplainStmt:
 			retval = _copyExplainStmt(from);
 			break;
+		case T_CreateTableAsStmt:
+			retval = _copyCreateTableAsStmt(from);
+			break;
 		case T_CreateSeqStmt:
 			retval = _copyCreateSeqStmt(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index f490a7a..e090e38 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -900,7 +900,6 @@ _equalQuery(Query *a, Query *b)
 	COMPARE_SCALAR_FIELD(canSetTag);
 	COMPARE_NODE_FIELD(utilityStmt);
 	COMPARE_SCALAR_FIELD(resultRelation);
-	COMPARE_NODE_FIELD(intoClause);
 	COMPARE_SCALAR_FIELD(hasAggs);
 	COMPARE_SCALAR_FIELD(hasWindowFuncs);
 	COMPARE_SCALAR_FIELD(hasSubLinks);
@@ -1560,6 +1559,17 @@ _equalExplainStmt(ExplainStmt *a, ExplainStmt *b)
 	return true;
 }
 
+
+static bool
+_equalCreateTableAsStmt(CreateTableAsStmt *a, CreateTableAsStmt *b)
+{
+	COMPARE_NODE_FIELD(query);
+	COMPARE_NODE_FIELD(into);
+	COMPARE_SCALAR_FIELD(is_select_into);
+
+	return true;
+}
+
 static bool
 _equalCreateSeqStmt(CreateSeqStmt *a, CreateSeqStmt *b)
 {
@@ -1903,7 +1913,6 @@ static bool
 _equalExecuteStmt(ExecuteStmt *a, ExecuteStmt *b)
 {
 	COMPARE_STRING_FIELD(name);
-	COMPARE_NODE_FIELD(into);
 	COMPARE_NODE_FIELD(params);
 
 	return true;
@@ -2786,6 +2795,9 @@ equal(void *a, void *b)
 		case T_ExplainStmt:
 			retval = _equalExplainStmt(a, b);
 			break;
+		case T_CreateTableAsStmt:
+			retval = _equalCreateTableAsStmt(a, b);
+			break;
 		case T_CreateSeqStmt:
 			retval = _equalCreateSeqStmt(a, b);
 			break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 31af47f..997f5e2 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -248,7 +248,6 @@ _outPlannedStmt(StringInfo str, PlannedStmt *node)
 	WRITE_NODE_FIELD(rtable);
 	WRITE_NODE_FIELD(resultRelations);
 	WRITE_NODE_FIELD(utilityStmt);
-	WRITE_NODE_FIELD(intoClause);
 	WRITE_NODE_FIELD(subplans);
 	WRITE_BITMAPSET_FIELD(rewindPlanIDs);
 	WRITE_NODE_FIELD(rowMarks);
@@ -2197,7 +2196,6 @@ _outQuery(StringInfo str, Query *node)
 		appendStringInfo(str, " :utilityStmt <>");
 
 	WRITE_INT_FIELD(resultRelation);
-	WRITE_NODE_FIELD(intoClause);
 	WRITE_BOOL_FIELD(hasAggs);
 	WRITE_BOOL_FIELD(hasWindowFuncs);
 	WRITE_BOOL_FIELD(hasSubLinks);
@@ -2813,7 +2811,7 @@ _outNode(StringInfo str, void *obj)
 			case T_RangeVar:
 				_outRangeVar(str, obj);
 				break;
-			case T_IntoClause:
+			case T_IntoClause:/*XXX: This could be removed but dim would need to add it right back*/
 				_outIntoClause(str, obj);
 				break;
 			case T_Var:
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index 3de20ad..8a35c4c 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -198,7 +198,6 @@ _readQuery(void)
 	READ_BOOL_FIELD(canSetTag);
 	READ_NODE_FIELD(utilityStmt);
 	READ_INT_FIELD(resultRelation);
-	READ_NODE_FIELD(intoClause);
 	READ_BOOL_FIELD(hasAggs);
 	READ_BOOL_FIELD(hasWindowFuncs);
 	READ_BOOL_FIELD(hasSubLinks);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 5c18b72..93b7773 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -233,7 +233,6 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
 	result->rtable = glob->finalrtable;
 	result->resultRelations = glob->resultRelations;
 	result->utilityStmt = parse->utilityStmt;
-	result->intoClause = parse->intoClause;
 	result->subplans = glob->subplans;
 	result->rewindPlanIDs = glob->rewindPlanIDs;
 	result->rowMarks = glob->finalrowmarks;
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index e396520..a995fdf 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1399,7 +1399,6 @@ simplify_EXISTS_query(Query *query)
 	 * are complex.
 	 */
 	if (query->commandType != CMD_SELECT ||
-		query->intoClause ||
 		query->setOperations ||
 		query->hasAggs ||
 		query->hasWindowFuncs ||
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 8bb011b..f71a988 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -1047,8 +1047,7 @@ is_simple_subquery(Query *subquery)
 	 */
 	if (!IsA(subquery, Query) ||
 		subquery->commandType != CMD_SELECT ||
-		subquery->utilityStmt != NULL ||
-		subquery->intoClause != NULL)
+		subquery->utilityStmt != NULL)
 		elog(ERROR, "subquery is bogus");
 
 	/*
@@ -1134,8 +1133,7 @@ is_simple_union_all(Query *subquery)
 	/* Let's just make sure it's a valid subselect ... */
 	if (!IsA(subquery, Query) ||
 		subquery->commandType != CMD_SELECT ||
-		subquery->utilityStmt != NULL ||
-		subquery->intoClause != NULL)
+		subquery->utilityStmt != NULL)
 		elog(ERROR, "subquery is bogus");
 
 	/* Is it a set-operation query at all? */
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index ad02950..7422387 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4028,7 +4028,6 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
 	if (!IsA(querytree, Query) ||
 		querytree->commandType != CMD_SELECT ||
 		querytree->utilityStmt ||
-		querytree->intoClause ||
 		querytree->hasAggs ||
 		querytree->hasWindowFuncs ||
 		querytree->hasSubLinks ||
@@ -4542,8 +4541,7 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
 	 */
 	if (!IsA(querytree, Query) ||
 		querytree->commandType != CMD_SELECT ||
-		querytree->utilityStmt ||
-		querytree->intoClause)
+		querytree->utilityStmt)
 		goto fail;
 
 	/*
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index dae5478..1d17f8b 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -62,6 +62,8 @@ static Query *transformDeclareCursorStmt(ParseState *pstate,
 						   DeclareCursorStmt *stmt);
 static Query *transformExplainStmt(ParseState *pstate,
 					 ExplainStmt *stmt);
+static Query *transformCreateTableAsStmt(ParseState *pstate,
+					 CreateTableAsStmt *stmt);
 static void transformLockingClause(ParseState *pstate, Query *qry,
 					   LockingClause *lc, bool pushedDown);
 
@@ -202,6 +204,11 @@ transformStmt(ParseState *pstate, Node *parseTree)
 										  (ExplainStmt *) parseTree);
 			break;
 
+		case T_CreateTableAsStmt:
+			result = transformCreateTableAsStmt(pstate,
+			                           (CreateTableAsStmt *) parseTree);
+			break;
+
 		default:
 
 			/*
@@ -257,6 +264,7 @@ analyze_requires_snapshot(Node *parseTree)
 			result = true;
 			break;
 
+		case T_CreateTableAsStmt:
 		case T_ExplainStmt:
 			/* yes, because we must analyze the contained statement */
 			result = true;
@@ -455,21 +463,29 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		sub_pstate->p_relnamespace = sub_relnamespace;
 		sub_pstate->p_varnamespace = sub_varnamespace;
 
+		/* The grammar should have produced a SELECT, but it might have INTO */
+		if(IsA(stmt->selectStmt, SelectStmt))
+		{
+			SelectStmt* first = (SelectStmt*)stmt->selectStmt;
+			while (first && first->op != SETOP_NONE)
+				first = first->larg;
+
+			if (first->intoClause)
+				ereport(ERROR,
+				        (errcode(ERRCODE_SYNTAX_ERROR),
+				         errmsg("INSERT ... SELECT cannot specify INTO"),
+				         parser_errposition(pstate,
+				                            exprLocation((Node *)first->intoClause))));
+		}
+
 		selectQuery = transformStmt(sub_pstate, stmt->selectStmt);
 
 		free_parsestate(sub_pstate);
 
-		/* The grammar should have produced a SELECT, but it might have INTO */
 		if (!IsA(selectQuery, Query) ||
 			selectQuery->commandType != CMD_SELECT ||
 			selectQuery->utilityStmt != NULL)
 			elog(ERROR, "unexpected non-SELECT command in INSERT ... SELECT");
-		if (selectQuery->intoClause)
-			ereport(ERROR,
-					(errcode(ERRCODE_SYNTAX_ERROR),
-					 errmsg("INSERT ... SELECT cannot specify INTO"),
-					 parser_errposition(pstate,
-						   exprLocation((Node *) selectQuery->intoClause))));
 
 		/*
 		 * Make the source be a subquery in the INSERT's rangetable, and add
@@ -876,6 +892,18 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	Node	   *qual;
 	ListCell   *l;
 
+	/*
+	 * Validity-check whether we got called from somewhere where
+	 * ... INTO was not allowed
+	 */
+	if (stmt->intoClause)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("INTO is only allowed on first SELECT of UNION/INTERSECT/EXCEPT"),
+				 parser_errposition(pstate,
+				                    exprLocation((Node *) stmt->intoClause))));
+
+
 	qry->commandType = CMD_SELECT;
 
 	/* process the WITH clause independently of all else */
@@ -963,8 +991,6 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 												   pstate->p_windowdefs,
 												   &qry->targetList);
 
-	/* SELECT INTO/CREATE TABLE AS spec is just passed through */
-	qry->intoClause = stmt->intoClause;
 
 	qry->rtable = pstate->p_rtable;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
@@ -1185,9 +1211,6 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 			 errmsg("SELECT FOR UPDATE/SHARE cannot be applied to VALUES")));
 
-	/* CREATE TABLE AS spec is just passed through */
-	qry->intoClause = stmt->intoClause;
-
 	/*
 	 * There mustn't have been any table references in the expressions, else
 	 * strange things would happen, like Cartesian products of those tables
@@ -1253,7 +1276,6 @@ static Query *
 transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 {
 	Query	   *qry = makeNode(Query);
-	SelectStmt *leftmostSelect;
 	int			leftmostRTI;
 	Query	   *leftmostQuery;
 	SetOperationStmt *sostmt;
@@ -1286,20 +1308,6 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	}
 
 	/*
-	 * Find leftmost leaf SelectStmt; extract the one-time-only items from it
-	 * and from the top-level node.
-	 */
-	leftmostSelect = stmt->larg;
-	while (leftmostSelect && leftmostSelect->op != SETOP_NONE)
-		leftmostSelect = leftmostSelect->larg;
-	Assert(leftmostSelect && IsA(leftmostSelect, SelectStmt) &&
-		   leftmostSelect->larg == NULL);
-	qry->intoClause = leftmostSelect->intoClause;
-
-	/* clear this to prevent complaints in transformSetOperationTree() */
-	leftmostSelect->intoClause = NULL;
-
-	/*
 	 * These are not one-time, exactly, but we want to process them here and
 	 * not let transformSetOperationTree() see them --- else it'll just
 	 * recurse right back here!
@@ -1330,7 +1338,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->setOperations = (Node *) sostmt;
 
 	/*
-	 * Re-find leftmost SELECT (now it's a sub-query in rangetable)
+	 * Find leftmost SELECT (it's a sub-query in rangetable)
 	 */
 	node = sostmt->larg;
 	while (node && IsA(node, SetOperationStmt))
@@ -2099,6 +2107,25 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt)
 				(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
 				 errmsg("cannot specify both SCROLL and NO SCROLL")));
 
+	/*
+	 * We must explicitly disallow DECLARE CURSOR ... SELECT INTO We
+	 * have to do this before transformStmt() as that will holler if
+	 * it ever finds a intoClause
+	 */
+	if(IsA(stmt->query, SelectStmt))
+	{
+		SelectStmt* first = (SelectStmt*)stmt->query;
+		while (first && first->op != SETOP_NONE)
+			first = first->larg;
+
+		if (first->intoClause)
+			ereport(ERROR,
+			        (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
+			         errmsg("DECLARE CURSOR cannot specify INTO"),
+			         parser_errposition(pstate,
+			                            exprLocation((Node *) first->intoClause))));
+	}
+
 	result = transformStmt(pstate, stmt->query);
 
 	/* Grammar should not have allowed anything but SELECT */
@@ -2107,14 +2134,6 @@ transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt)
 		result->utilityStmt != NULL)
 		elog(ERROR, "unexpected non-SELECT command in DECLARE CURSOR");
 
-	/* But we must explicitly disallow DECLARE CURSOR ... SELECT INTO */
-	if (result->intoClause)
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
-				 errmsg("DECLARE CURSOR cannot specify INTO"),
-				 parser_errposition(pstate,
-								exprLocation((Node *) result->intoClause))));
-
 	/*
 	 * We also disallow data-modifying WITH in a cursor.  (This could be
 	 * allowed, but the semantics of when the updates occur might be
@@ -2183,6 +2202,28 @@ transformExplainStmt(ParseState *pstate, ExplainStmt *stmt)
 
 
 /*
+ * transformCreateTableAsStmt -
+ *	transform an CREATE TABLE AS/SELECT ... INTO Statement
+ *
+ */
+static Query *
+transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
+{
+	Query	   *result;
+
+	/* transform contained query */
+	stmt->query = (Node *) transformStmt(pstate, stmt->query);
+
+	/* represent the command as a utility Query */
+	result = makeNode(Query);
+	result->commandType = CMD_UTILITY;
+	result->utilityStmt = (Node *) stmt;
+
+	return result;
+}
+
+
+/*
  * Check for features that are not supported together with FOR UPDATE/SHARE.
  *
  * exported so planner can check again after rewriting, query pullup, etc
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2a497d1..fe1bddc 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3010,23 +3010,28 @@ ExistingIndex:   USING INDEX index_name				{ $$ = $3; }
 CreateAsStmt:
 		CREATE OptTemp TABLE create_as_target AS SelectStmt opt_with_data
 				{
+                    SelectStmt *n;
+					CreateTableAsStmt* ctas = makeNode(CreateTableAsStmt);
+                    ctas->into = $4;
+                    ctas->query = $6;
+                    ctas->is_select_into = false;
+                    $4->skipData = !($7);
+					$4->rel->relpersistence = $2;
+					$4->skipData = !($7);
+                    $$ = (Node*)ctas;
+
 					/*
 					 * When the SelectStmt is a set-operation tree, we must
 					 * stuff the INTO information into the leftmost component
 					 * Select, because that's where analyze.c will expect
 					 * to find it.
 					 */
-					SelectStmt *n = findLeftmostSelect((SelectStmt *) $6);
+					n = findLeftmostSelect((SelectStmt *) $6);
 					if (n->intoClause != NULL)
 						ereport(ERROR,
 								(errcode(ERRCODE_SYNTAX_ERROR),
 								 errmsg("CREATE TABLE AS cannot specify INTO"),
 								 parser_errposition(exprLocation((Node *) n->intoClause))));
-					n->intoClause = $4;
-					/* cram additional flags into the IntoClause */
-					$4->rel->relpersistence = $2;
-					$4->skipData = !($7);
-					$$ = $6;
 				}
 		;
 
@@ -7994,20 +7999,25 @@ ExecuteStmt: EXECUTE name execute_param_clause
 					ExecuteStmt *n = makeNode(ExecuteStmt);
 					n->name = $2;
 					n->params = $3;
-					n->into = NULL;
 					$$ = (Node *) n;
 				}
 			| CREATE OptTemp TABLE create_as_target AS
 				EXECUTE name execute_param_clause opt_with_data
 				{
 					ExecuteStmt *n = makeNode(ExecuteStmt);
+					CreateTableAsStmt* ctas = makeNode(CreateTableAsStmt);
+
 					n->name = $7;
 					n->params = $8;
-					n->into = $4;
+
 					/* cram additional flags into the IntoClause */
 					$4->rel->relpersistence = $2;
 					$4->skipData = !($9);
-					$$ = (Node *) n;
+
+                    ctas->into = $4;
+                    ctas->query = (Node*)n;
+                    ctas->is_select_into = false;
+					$$ = (Node *) ctas;
 				}
 		;
 
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index e8177bc..4758835 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -481,6 +481,21 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
 	if (r->alias == NULL)
 		elog(ERROR, "subquery in FROM must have an alias");
 
+
+	if(IsA(r->subquery, SelectStmt))
+	{
+		SelectStmt* first = (SelectStmt*)r->subquery;
+		while (first && first->op != SETOP_NONE)
+			first = first->larg;
+
+		if (first->intoClause)
+			ereport(ERROR,
+			        (errcode(ERRCODE_SYNTAX_ERROR),
+			         errmsg("subquery in FROM cannot have SELECT INTO"),
+			         parser_errposition(pstate,
+			                            exprLocation((Node *)first->intoClause))));
+	}
+
 	/*
 	 * Analyze and transform the subquery.
 	 */
@@ -495,12 +510,6 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
 		query->commandType != CMD_SELECT ||
 		query->utilityStmt != NULL)
 		elog(ERROR, "unexpected non-SELECT command in subquery in FROM");
-	if (query->intoClause)
-		ereport(ERROR,
-				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("subquery in FROM cannot have SELECT INTO"),
-				 parser_errposition(pstate,
-								 exprLocation((Node *) query->intoClause))));
 
 	/*
 	 * The subquery cannot make use of any variables from FROM items created
diff --git a/src/backend/parser/parse_cte.c b/src/backend/parser/parse_cte.c
index ec6afd8..299e609 100644
--- a/src/backend/parser/parse_cte.c
+++ b/src/backend/parser/parse_cte.c
@@ -241,6 +241,20 @@ analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
 	/* Analysis not done already */
 	Assert(!IsA(cte->ctequery, Query));
 
+	if(IsA(cte->ctequery, SelectStmt))
+	{
+		SelectStmt* first = (SelectStmt*)cte->ctequery;
+		while (first && first->op != SETOP_NONE)
+			first = first->larg;
+
+		if (first->intoClause)
+			ereport(ERROR,
+			        (errcode(ERRCODE_SYNTAX_ERROR),
+			         errmsg("subquery in WITH cannot have SELECT INTO"),
+			         parser_errposition(pstate,
+			                            exprLocation((Node *) first->intoClause))));
+	}
+
 	query = parse_sub_analyze(cte->ctequery, pstate, cte, false);
 	cte->ctequery = (Node *) query;
 
@@ -253,13 +267,6 @@ analyzeCTE(ParseState *pstate, CommonTableExpr *cte)
 	if (query->utilityStmt != NULL)
 		elog(ERROR, "unexpected utility statement in WITH");
 
-	if (query->intoClause)
-		ereport(ERROR,
-				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("subquery in WITH cannot have SELECT INTO"),
-				 parser_errposition(pstate,
-								 exprLocation((Node *) query->intoClause))));
-
 	/*
 	 * We disallow data-modifying WITH except at the top level of a query,
 	 * because it's not clear when such a modification should be executed.
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 75236c7..ff8b7c5 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -1398,6 +1398,21 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		return result;
 
 	pstate->p_hasSubLinks = true;
+
+	if(IsA(sublink->subselect, SelectStmt))
+	{
+		SelectStmt* first = (SelectStmt*)sublink->subselect;
+		while (first && first->op != SETOP_NONE)
+			first = first->larg;
+
+		if (first->intoClause)
+			ereport(ERROR,
+			        (errcode(ERRCODE_SYNTAX_ERROR),
+			         errmsg("subquery cannot have SELECT INTO"),
+			         parser_errposition(pstate,
+			                            exprLocation((Node *) first->intoClause))));
+	}
+
 	qtree = parse_sub_analyze(sublink->subselect, pstate, NULL, false);
 
 	/*
@@ -1408,12 +1423,6 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
 		qtree->commandType != CMD_SELECT ||
 		qtree->utilityStmt != NULL)
 		elog(ERROR, "unexpected non-SELECT command in SubLink");
-	if (qtree->intoClause)
-		ereport(ERROR,
-				(errcode(ERRCODE_SYNTAX_ERROR),
-				 errmsg("subquery cannot have SELECT INTO"),
-				 parser_errposition(pstate,
-								 exprLocation((Node *) qtree->intoClause))));
 
 	sublink->subselect = (Node *) qtree;
 
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index e389208..de28d84 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -37,6 +37,7 @@ raw_parser(const char *str)
 	core_yyscan_t yyscanner;
 	base_yy_extra_type yyextra;
 	int			yyresult;
+	ListCell *c;
 
 	/* initialize the flex scanner */
 	yyscanner = scanner_init(str, &yyextra.core_yy_extra,
@@ -57,6 +58,43 @@ raw_parser(const char *str)
 	if (yyresult)				/* error */
 		return NIL;
 
+	/*
+	 * Some things are rather hard to properly diagnose in grammar
+	 * without complicating/duplicating it too much. So we do some
+	 * postprocessing here.
+	 */
+	foreach(c, yyextra.parsetree){
+		switch(nodeTag(lfirst(c))){
+			/*
+			 * The grammar currently doesn't disambiguate between
+			 * SELECT and SELECT ... INTO. Do that now.
+			 */
+			case T_SelectStmt:
+			{
+				SelectStmt* s = (SelectStmt*)lfirst(c);
+				SelectStmt* first = s;
+				while (first && first->op != SETOP_NONE)
+					first = first->larg;
+				Assert(first && IsA(first, SelectStmt) && first->larg == NULL);
+				if(first->intoClause){
+					CreateTableAsStmt* ctas = makeNode(CreateTableAsStmt);
+					ctas->into = first->intoClause;
+					ctas->query = (Node*)s;
+					ctas->is_select_into = true;
+					/*
+					 * this way everyone can complain if this is set
+					 * without further checks because it shall never
+					 * be set but here.
+					 */
+					first->intoClause = NULL;
+					lfirst(c) = ctas;
+					break;
+				}
+			}
+			default:
+				break;
+		}
+	}
 	return yyextra.parsetree;
 }
 
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 17db70e..e359e9a 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -324,8 +324,7 @@ DefineQueryRewrite(char *rulename,
 		query = (Query *) linitial(action);
 		if (!is_instead ||
 			query->commandType != CMD_SELECT ||
-			query->utilityStmt != NULL ||
-			query->intoClause != NULL)
+			query->utilityStmt != NULL)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("rules on SELECT must have action INSTEAD SELECT")));
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 466727b..2628e31 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -260,8 +260,7 @@ ChoosePortalStrategy(List *stmts)
 			if (query->canSetTag)
 			{
 				if (query->commandType == CMD_SELECT &&
-					query->utilityStmt == NULL &&
-					query->intoClause == NULL)
+					query->utilityStmt == NULL)
 				{
 					if (query->hasModifyingCTE)
 						return PORTAL_ONE_MOD_WITH;
@@ -285,8 +284,7 @@ ChoosePortalStrategy(List *stmts)
 			if (pstmt->canSetTag)
 			{
 				if (pstmt->commandType == CMD_SELECT &&
-					pstmt->utilityStmt == NULL &&
-					pstmt->intoClause == NULL)
+					pstmt->utilityStmt == NULL)
 				{
 					if (pstmt->hasModifyingCTE)
 						return PORTAL_ONE_MOD_WITH;
@@ -395,8 +393,7 @@ FetchStatementTargetList(Node *stmt)
 		else
 		{
 			if (query->commandType == CMD_SELECT &&
-				query->utilityStmt == NULL &&
-				query->intoClause == NULL)
+				query->utilityStmt == NULL)
 				return query->targetList;
 			if (query->returningList)
 				return query->returningList;
@@ -408,8 +405,7 @@ FetchStatementTargetList(Node *stmt)
 		PlannedStmt *pstmt = (PlannedStmt *) stmt;
 
 		if (pstmt->commandType == CMD_SELECT &&
-			pstmt->utilityStmt == NULL &&
-			pstmt->intoClause == NULL)
+			pstmt->utilityStmt == NULL)
 			return pstmt->planTree->targetlist;
 		if (pstmt->hasReturning)
 			return pstmt->planTree->targetlist;
@@ -430,7 +426,6 @@ FetchStatementTargetList(Node *stmt)
 		ExecuteStmt *estmt = (ExecuteStmt *) stmt;
 		PreparedStatement *entry;
 
-		Assert(!estmt->into);
 		entry = FetchPreparedStatement(estmt->name, true);
 		return FetchPreparedStatementTargetList(entry);
 	}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 6f88c47..4c5be0a 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -127,9 +127,7 @@ CommandIsReadOnly(Node *parsetree)
 		switch (stmt->commandType)
 		{
 			case CMD_SELECT:
-				if (stmt->intoClause != NULL)
-					return false;		/* SELECT INTO */
-				else if (stmt->rowMarks != NIL)
+				if (stmt->rowMarks != NIL)
 					return false;		/* SELECT FOR UPDATE/SHARE */
 				else if (stmt->hasModifyingCTE)
 					return false;		/* data-modifying CTE */
@@ -198,6 +196,7 @@ check_xact_readonly(Node *parsetree)
 		case T_CreateSchemaStmt:
 		case T_CreateSeqStmt:
 		case T_CreateStmt:
+		case T_CreateTableAsStmt:
 		case T_CreateTableSpaceStmt:
 		case T_CreateTrigStmt:
 		case T_CompositeTypeStmt:
@@ -1017,6 +1016,10 @@ standard_ProcessUtility(Node *parsetree,
 			ExplainQuery((ExplainStmt *) parsetree, queryString, params, dest);
 			break;
 
+		case T_CreateTableAsStmt:
+			CreateTableAs((CreateTableAsStmt *) parsetree, queryString, params, dest);
+			break;
+
 		case T_VariableSetStmt:
 			ExecSetVariableStmt((VariableSetStmt *) parsetree);
 			break;
@@ -1211,8 +1214,6 @@ UtilityReturnsTuples(Node *parsetree)
 				ExecuteStmt *stmt = (ExecuteStmt *) parsetree;
 				PreparedStatement *entry;
 
-				if (stmt->into)
-					return false;
 				entry = FetchPreparedStatement(stmt->name, false);
 				if (!entry)
 					return false;		/* not our business to raise error */
@@ -1263,8 +1264,6 @@ UtilityTupleDescriptor(Node *parsetree)
 				ExecuteStmt *stmt = (ExecuteStmt *) parsetree;
 				PreparedStatement *entry;
 
-				if (stmt->into)
-					return NULL;
 				entry = FetchPreparedStatement(stmt->name, false);
 				if (!entry)
 					return NULL;	/* not our business to raise error */
@@ -1299,8 +1298,7 @@ QueryReturnsTuples(Query *parsetree)
 	{
 		case CMD_SELECT:
 			/* returns tuples ... unless it's DECLARE CURSOR or SELECT INTO */
-			if (parsetree->utilityStmt == NULL &&
-				parsetree->intoClause == NULL)
+			if (parsetree->utilityStmt == NULL)
 				return true;
 			break;
 		case CMD_INSERT:
@@ -1888,6 +1886,13 @@ CreateCommandTag(Node *parsetree)
 			tag = "EXPLAIN";
 			break;
 
+		case T_CreateTableAsStmt:
+			if (((CreateTableAsStmt*)parsetree)->is_select_into)
+				tag = "SELECT INTO";
+			else
+				tag = "CREATE TABLE AS";
+			break;
+
 		case T_VariableSetStmt:
 			switch (((VariableSetStmt *) parsetree)->kind)
 			{
@@ -2041,8 +2046,6 @@ CreateCommandTag(Node *parsetree)
 							Assert(IsA(stmt->utilityStmt, DeclareCursorStmt));
 							tag = "DECLARE CURSOR";
 						}
-						else if (stmt->intoClause != NULL)
-							tag = "SELECT INTO";
 						else if (stmt->rowMarks != NIL)
 						{
 							/* not 100% but probably close enough */
@@ -2091,8 +2094,6 @@ CreateCommandTag(Node *parsetree)
 							Assert(IsA(stmt->utilityStmt, DeclareCursorStmt));
 							tag = "DECLARE CURSOR";
 						}
-						else if (stmt->intoClause != NULL)
-							tag = "SELECT INTO";
 						else if (stmt->rowMarks != NIL)
 						{
 							/* not 100% but probably close enough */
@@ -2159,10 +2160,7 @@ GetCommandLogLevel(Node *parsetree)
 			break;
 
 		case T_SelectStmt:
-			if (((SelectStmt *) parsetree)->intoClause)
-				lev = LOGSTMT_DDL;		/* CREATE AS, SELECT INTO */
-			else
-				lev = LOGSTMT_ALL;
+			lev = LOGSTMT_ALL;
 			break;
 
 			/* utility statements --- same whether raw or cooked */
@@ -2410,6 +2408,10 @@ GetCommandLogLevel(Node *parsetree)
 			}
 			break;
 
+		case T_CreateTableAsStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
 		case T_VariableSetStmt:
 			lev = LOGSTMT_ALL;
 			break;
@@ -2510,10 +2512,7 @@ GetCommandLogLevel(Node *parsetree)
 				switch (stmt->commandType)
 				{
 					case CMD_SELECT:
-						if (stmt->intoClause != NULL)
-							lev = LOGSTMT_DDL;	/* CREATE AS, SELECT INTO */
-						else
-							lev = LOGSTMT_ALL;	/* SELECT or DECLARE CURSOR */
+						lev = LOGSTMT_ALL;
 						break;
 
 					case CMD_UPDATE:
@@ -2539,10 +2538,7 @@ GetCommandLogLevel(Node *parsetree)
 				switch (stmt->commandType)
 				{
 					case CMD_SELECT:
-						if (stmt->intoClause != NULL)
-							lev = LOGSTMT_DDL;	/* CREATE AS, SELECT INTO */
-						else
-							lev = LOGSTMT_ALL;	/* SELECT or DECLARE CURSOR */
+						lev = LOGSTMT_ALL;
 						break;
 
 					case CMD_UPDATE:
diff --git a/src/include/access/htup.h b/src/include/access/htup.h
index 3ca25ac..01e3049 100644
--- a/src/include/access/htup.h
+++ b/src/include/access/htup.h
@@ -311,7 +311,7 @@ do { \
 
 #define HeapTupleHeaderSetOid(tup, oid) \
 do { \
-	Assert((tup)->t_infomask & HEAP_HASOID); \
+	if(!((tup)->t_infomask & HEAP_HASOID)) elog(ERROR, "HEAP_HASOID"); \
 	*((Oid *) ((char *)(tup) + (tup)->t_hoff - sizeof(Oid))) = (oid); \
 } while (0)
 
diff --git a/src/include/commands/prepare.h b/src/include/commands/prepare.h
index 52362fa..e0c2571 100644
--- a/src/include/commands/prepare.h
+++ b/src/include/commands/prepare.h
@@ -54,4 +54,7 @@ extern List *FetchPreparedStatementTargetList(PreparedStatement *stmt);
 
 void		DropAllPreparedStatements(void);
 
+extern ParamListInfo EvaluateParams(PreparedStatement *pstmt, List *params,
+                                    const char *queryString, EState *estate);
+
 #endif   /* PREPARE_H */
diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h
index 333e303..bac53ac 100644
--- a/src/include/commands/tablecmds.h
+++ b/src/include/commands/tablecmds.h
@@ -15,6 +15,7 @@
 #define TABLECMDS_H
 
 #include "access/htup.h"
+#include "executor/executor.h"
 #include "nodes/parsenodes.h"
 #include "storage/lock.h"
 #include "utils/relcache.h"
@@ -22,6 +23,9 @@
 
 extern Oid	DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId);
 
+extern void CreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
+			 ParamListInfo params, DestReceiver *dest);
+
 extern void RemoveRelations(DropStmt *drop);
 
 extern void AlterTable(AlterTableStmt *stmt);
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index bdd499b..a029984 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -49,12 +49,17 @@
  * AfterTriggerBeginQuery/AfterTriggerEndQuery.  This does not necessarily
  * mean that the plan can't queue any AFTER triggers; just that the caller
  * is responsible for there being a trigger context for them to be queued in.
+ *
+ * FORCE_OIDS tells the executor to build a tupleDesc that includes
+ * the oid column. This is used from tablecmd.c's CreateTableAs.
  */
 #define EXEC_FLAG_EXPLAIN_ONLY	0x0001	/* EXPLAIN, no ANALYZE */
 #define EXEC_FLAG_REWIND		0x0002	/* need efficient rescan */
 #define EXEC_FLAG_BACKWARD		0x0004	/* need backward scan */
 #define EXEC_FLAG_MARK			0x0008	/* need mark/restore */
 #define EXEC_FLAG_SKIP_TRIGGERS 0x0010	/* skip AfterTrigger calls */
+#define EXEC_FLAG_FORCE_OIDS_ON 0x0020	/* force oids in returned tuples */
+#define EXEC_FLAG_FORCE_OIDS_OFF 0x0040	/* force oids in returned tuples */
 
 
 /*
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 0a89f18..4f9c569 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -321,6 +321,12 @@ typedef struct ResultRelInfo
 	ProjectionInfo *ri_projectReturning;
 } ResultRelInfo;
 
+typedef enum {
+	ESTATE_FORCE_OIDS_DEFAULT,
+	ESTATE_FORCE_OIDS_ON,
+	ESTATE_FORCE_OIDS_OFF
+} EStateForceOids;
+
 /* ----------------
  *	  EState information
  *
@@ -370,8 +376,7 @@ typedef struct EState
 
 	int			es_top_eflags;	/* eflags passed to ExecutorStart */
 	int			es_instrument;	/* OR of InstrumentOption flags */
-	bool		es_select_into; /* true if doing SELECT INTO */
-	bool		es_into_oids;	/* true to generate OIDs in SELECT INTO */
+	EStateForceOids        es_force_oids;  /* used by CreateTableAs */
 	bool		es_finished;	/* true when ExecutorFinish is done */
 
 	List	   *es_exprcontexts;	/* List of ExprContexts within EState */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 3a24089..3f48b3f 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -305,6 +305,7 @@ typedef enum NodeTag
 	T_DropdbStmt,
 	T_VacuumStmt,
 	T_ExplainStmt,
+	T_CreateTableAsStmt,
 	T_CreateSeqStmt,
 	T_AlterSeqStmt,
 	T_VariableSetStmt,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 9e277c5..6577660 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -111,8 +111,6 @@ typedef struct Query
 	int			resultRelation; /* rtable index of target relation for
 								 * INSERT/UPDATE/DELETE; 0 for SELECT */
 
-	IntoClause *intoClause;		/* target for SELECT INTO / CREATE TABLE AS */
-
 	bool		hasAggs;		/* has aggregates in tlist or havingQual */
 	bool		hasWindowFuncs; /* has window functions in tlist */
 	bool		hasSubLinks;	/* has subquery SubLink */
@@ -1008,7 +1006,7 @@ typedef struct SelectStmt
 	 */
 	List	   *distinctClause; /* NULL, list of DISTINCT ON exprs, or
 								 * lcons(NIL,NIL) for all (SELECT DISTINCT) */
-	IntoClause *intoClause;		/* target for SELECT INTO / CREATE TABLE AS */
+	IntoClause *intoClause;		/* target for SELECT INTO */
 	List	   *targetList;		/* the target list (of ResTarget) */
 	List	   *fromClause;		/* the FROM clause */
 	Node	   *whereClause;	/* WHERE qualification */
@@ -2373,7 +2371,7 @@ typedef struct VacuumStmt
  *		Explain Statement
  *
  * The "query" field is either a raw parse tree (SelectStmt, InsertStmt, etc)
- * or a Query node if parse analysis has been done.  Note that rewriting and
+ * or a Query node if parse analysis has been done. Note that rewriting and
  * planning of the query are always postponed until execution of EXPLAIN.
  * ----------------------
  */
@@ -2385,6 +2383,19 @@ typedef struct ExplainStmt
 } ExplainStmt;
 
 /* ----------------------
+ * analyzing, rewriting and planning are handled as in explain
+ * statements (see comment above) only that query can only be a
+ * SelectStmt and not some other type.
+ */
+typedef struct CreateTableAsStmt
+{
+	NodeTag		type;
+	IntoClause  *into;
+	Node        *query;			/* the query (see comments above) */
+	bool        is_select_into; /* plpgsql wants to disambiguate */
+} CreateTableAsStmt;
+
+/* ----------------------
  * Checkpoint Statement
  * ----------------------
  */
@@ -2498,7 +2509,6 @@ typedef struct ExecuteStmt
 {
 	NodeTag		type;
 	char	   *name;			/* The name of the plan to execute */
-	IntoClause *into;			/* Optional table to store results in */
 	List	   *params;			/* Values to assign to parameters */
 } ExecuteStmt;
 
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 6685864..acaae3e 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -54,8 +54,6 @@ typedef struct PlannedStmt
 
 	Node	   *utilityStmt;	/* non-null if this is DECLARE CURSOR */
 
-	IntoClause *intoClause;		/* target for SELECT INTO / CREATE TABLE AS */
-
 	List	   *subplans;		/* Plan trees for SubPlan expressions */
 
 	Bitmapset  *rewindPlanIDs;	/* indices of subplans that require REWIND */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 717ad79..eee27d3 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -3271,29 +3271,18 @@ exec_stmt_dynexecute(PLpgSQL_execstate *estate,
 			break;
 
 		case SPI_OK_SELINTO:
-
 			/*
 			 * We want to disallow SELECT INTO for now, because its behavior
 			 * is not consistent with SELECT INTO in a normal plpgsql context.
 			 * (We need to reimplement EXECUTE to parse the string as a
 			 * plpgsql command, not just feed it to SPI_execute.) However,
-			 * CREATE AS should be allowed ... and since it produces the same
-			 * parsetree as SELECT INTO, there's no way to tell the difference
-			 * except to look at the source text.  Wotta kluge!
+			 * CREATE AS should be allowed ...
 			 */
-			{
-				char	   *ptr;
-
-				for (ptr = querystr; *ptr; ptr++)
-					if (!scanner_isspace(*ptr))
-						break;
-				if (*ptr == 'S' || *ptr == 's')
-					ereport(ERROR,
-							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("EXECUTE of SELECT ... INTO is not implemented"),
-							 errhint("You might want to use EXECUTE ... INTO or EXECUTE CREATE TABLE ... AS instead.")));
-				break;
-			}
+			ereport(ERROR,
+			        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			         errmsg("EXECUTE of SELECT ... INTO is not implemented"),
+			         errhint("You might want to use EXECUTE ... INTO or EXECUTE CREATE TABLE ... AS instead.")));
+			break;
 
 			/* Some SPI errors deserve specific error messages */
 		case SPI_ERROR_COPY:
@@ -5733,7 +5722,7 @@ exec_simple_check_plan(PLpgSQL_expr *expr)
 	 */
 	if (!IsA(query, Query))
 		return;
-	if (query->commandType != CMD_SELECT || query->intoClause)
+	if (query->commandType != CMD_SELECT)
 		return;
 	if (query->rtable != NIL)
 		return;
@@ -5807,7 +5796,7 @@ exec_simple_recheck_plan(PLpgSQL_expr *expr, CachedPlan *cplan)
 	 */
 	if (!IsA(stmt, PlannedStmt))
 		return;
-	if (stmt->commandType != CMD_SELECT || stmt->intoClause)
+	if (stmt->commandType != CMD_SELECT)
 		return;
 	plan = stmt->planTree;
 	if (!IsA(plan, Result))
diff --git a/src/test/regress/expected/select_into.out b/src/test/regress/expected/select_into.out
index 9ed4229..20bbc16 100644
--- a/src/test/regress/expected/select_into.out
+++ b/src/test/regress/expected/select_into.out
@@ -44,6 +44,25 @@ CREATE TABLE selinto_schema.tmp3 (a,b,c)
 	   AS SELECT oid,relname,relacl FROM pg_class
 	   WHERE relname like '%c%';	-- OK
 RESET SESSION AUTHORIZATION;
+--
+-- disallowed uses of SELECT ... INTO. All should fail
+--
+DECLARE foo CURSOR FOR SELECT 1 INTO b;
+ERROR:  DECLARE CURSOR cannot specify INTO
+LINE 1: DECLARE foo CURSOR FOR SELECT 1 INTO b;
+                                             ^
+COPY (SELECT 1 INTO frak UNION SELECT 2) TO 'blub';
+ERROR:  COPY (SELECT INTO) is not supported
+SELECT * FROM (SELECT 1 INTO f) bar;
+ERROR:  subquery in FROM cannot have SELECT INTO
+LINE 1: SELECT * FROM (SELECT 1 INTO f) bar;
+                                     ^
+CREATE VIEW foo AS SELECT 1 INTO b;
+ERROR:  views must not contain SELECT INTO
+INSERT INTO b SELECT 1 INTO f;
+ERROR:  INSERT ... SELECT cannot specify INTO
+LINE 1: INSERT INTO b SELECT 1 INTO f;
+                                    ^
 DROP SCHEMA selinto_schema CASCADE;
 NOTICE:  drop cascades to 3 other objects
 DETAIL:  drop cascades to table selinto_schema.tmp1
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index f49ec0e..2da50cf 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -139,7 +139,7 @@ SELECT * FROM writetest, temptest; -- ok
 (0 rows)
 
 CREATE TABLE test AS SELECT * FROM writetest; -- fail
-ERROR:  cannot execute SELECT INTO in a read-only transaction
+ERROR:  cannot execute CREATE TABLE AS in a read-only transaction
 START TRANSACTION READ WRITE;
 DROP TABLE writetest; -- ok
 COMMIT;
diff --git a/src/test/regress/sql/select_into.sql b/src/test/regress/sql/select_into.sql
index 039d35c..f04fd7a 100644
--- a/src/test/regress/sql/select_into.sql
+++ b/src/test/regress/sql/select_into.sql
@@ -50,5 +50,20 @@ CREATE TABLE selinto_schema.tmp3 (a,b,c)
 	   WHERE relname like '%c%';	-- OK
 RESET SESSION AUTHORIZATION;
 
+
+--
+-- disallowed uses of SELECT ... INTO. All should fail
+--
+DECLARE foo CURSOR FOR SELECT 1 INTO b;
+
+COPY (SELECT 1 INTO frak UNION SELECT 2) TO 'blub';
+
+SELECT * FROM (SELECT 1 INTO f) bar;
+
+CREATE VIEW foo AS SELECT 1 INTO b;
+
+INSERT INTO b SELECT 1 INTO f;
+
+
 DROP SCHEMA selinto_schema CASCADE;
 DROP USER selinto_user;
-- 
1.7.6.409.ge7a85.dirty