You are viewing a plain text version of this content. The canonical link for it is here.
Posted to derby-commits@db.apache.org by rh...@apache.org on 2013/10/24 14:42:00 UTC

svn commit: r1535360 [1/2] - in /db/derby/code/trunk/java: engine/org/apache/derby/iapi/services/io/ engine/org/apache/derby/iapi/sql/ engine/org/apache/derby/iapi/sql/execute/ engine/org/apache/derby/impl/sql/ engine/org/apache/derby/impl/sql/compile/...

Author: rhillegas
Date: Thu Oct 24 12:41:59 2013
New Revision: 1535360

URL: http://svn.apache.org/r1535360
Log:
DERBY-3155: Add support for DELETE action of MERGE statement; checking in derby-3155-04-af-deleteAction.diff.

Added:
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/MatchingClauseConstantAction.java   (with props)
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/MergeConstantAction.java   (with props)
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/MergeResultSet.java   (with props)
Modified:
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/io/RegisteredFormatIds.java
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/io/StoredFormatIds.java
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/Activation.java
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/execute/ConstantAction.java
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/execute/ResultSetFactory.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/GenericActivationHolder.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/CurrentOfNode.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/DMLModStatementNode.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/DeleteNode.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/FromBaseTable.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/InsertNode.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/MatchingClauseNode.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/MergeNode.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/QueryTreeNode.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/UpdateNode.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/sqlgrammar.jj
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/BaseActivation.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/BulkTableScanResultSet.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/DMLWriteResultSet.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/DeleteResultSet.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/GenericConstantActionFactory.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/GenericResultSetFactory.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/HashScanResultSet.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/ScanResultSet.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/store/access/heap/HeapRowLocation.java
    db/derby/code/trunk/java/engine/org/apache/derby/loc/messages.xml
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/GeneratedColumnsHelper.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/MergeStatementTest.java

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/io/RegisteredFormatIds.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/io/RegisteredFormatIds.java?rev=1535360&r1=1535359&r2=1535360&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/io/RegisteredFormatIds.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/io/RegisteredFormatIds.java Thu Oct 24 12:41:59 2013
@@ -534,6 +534,8 @@ private static final    String[] TwoByte
         /* 473 */       "org.apache.derby.impl.sql.catalog.CoreDDFinderClassInfo",
         /* 474 */       "org.apache.derby.catalog.types.UDTAliasInfo",
         /* 475 */       "org.apache.derby.catalog.types.AggregateAliasInfo",
+        /* 476 */       "org.apache.derby.impl.sql.execute.MatchingClauseConstantAction",
+        /* 477 */       "org.apache.derby.impl.sql.execute.MergeConstantAction",
 };
 
     /** Return the number of two-byte format ids */

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/io/StoredFormatIds.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/io/StoredFormatIds.java?rev=1535360&r1=1535359&r2=1535360&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/io/StoredFormatIds.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/io/StoredFormatIds.java Thu Oct 24 12:41:59 2013
@@ -952,6 +952,18 @@ public interface StoredFormatIds {
             (MIN_ID_2 + 39);
 
     /**
+        class org.apache.derby.impl.sql.execute.MatchingClauseConstantAction
+     */
+    static public final int MATCHING_CLAUSE_CONSTANT_ACTION_V01_ID =
+            (MIN_ID_2 + 476);
+
+    /**
+        class org.apache.derby.impl.sql.execute.MatchingClauseConstantAction
+     */
+    static public final int MERGE_CONSTANT_ACTION_V01_ID =
+            (MIN_ID_2 + 477);
+
+    /**
      */
     static public final int UNUSED_2_204 =
             (MIN_ID_2 + 204);
@@ -1679,7 +1691,7 @@ public interface StoredFormatIds {
      * Make sure this is updated when a new module is added
      */
     public static final int MAX_ID_2 =
-            (MIN_ID_2 + 475);
+            (MIN_ID_2 + 477);
 
     // DO NOT USE 4 BYTE IDS ANYMORE
     static public final int MAX_ID_4 =

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/Activation.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/Activation.java?rev=1535360&r1=1535359&r2=1535360&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/Activation.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/Activation.java Thu Oct 24 12:41:59 2013
@@ -544,6 +544,19 @@ public interface Activation extends Depe
 	 */
 	public java.sql.ResultSet getTargetVTI();
 
+    /**
+     * Push a ConstantAction to be returned by getConstantAction().
+     * Returns the newConstantAction.
+     */
+    public  ConstantAction    pushConstantAction( ConstantAction newConstantAction );
+
+    /**
+     * Pop the ConstantAction stack, returning the element which was just popped
+     * off the stack.
+     */
+    public  ConstantAction    popConstantAction();
+
+    /** Get the top ConstantAction on the stack without changing the stack. */
 	public ConstantAction	getConstantAction();
 
 	//store a reference to the parent table result sets

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/execute/ConstantAction.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/execute/ConstantAction.java?rev=1535360&r1=1535359&r2=1535360&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/execute/ConstantAction.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/execute/ConstantAction.java Thu Oct 24 12:41:59 2013
@@ -41,6 +41,13 @@ import org.apache.derby.catalog.UUID;
 
 public interface ConstantAction
 {
+    /** clauseType for WHEN NOT MATCHED ... THEN INSERT */
+    public  static  final   int WHEN_NOT_MATCHED_THEN_INSERT = 0;
+    /** clauseType for WHEN MATCHED ... THEN UPDATE */
+    public  static  final   int WHEN_MATCHED_THEN_UPDATE = 1;
+    /** clauseType for WHEN MATCHED ... THEN DELETE */
+    public  static  final   int WHEN_MATCHED_THEN_DELETE = 2;
+
 	/**
 	 *	Run the ConstantAction.
 	 *

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/execute/ResultSetFactory.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/execute/ResultSetFactory.java?rev=1535360&r1=1535359&r2=1535360&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/execute/ResultSetFactory.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/execute/ResultSetFactory.java Thu Oct 24 12:41:59 2013
@@ -169,6 +169,20 @@ public interface ResultSetFactory {
 							throws StandardException;
 
 	/**
+		A MERGE result set simply reports that it completed, and
+		the number of rows that it INSERTed/UPDATEd/DELETEdd.  It does not return rows.
+		The delete has been completed once the
+		MERGE result set is available.
+
+		@param drivingLeftJoin the result set from which to take rows to
+			be drive the INSERT/UPDATE/DELETE operations.
+		@return the MERGE operation as a result set.
+		@exception StandardException thrown when unable to perform the work
+	 */
+	ResultSet getMergeResultSet(NoPutResultSet drivingLeftJoin)
+							throws StandardException;
+
+	/**
 		A delete Cascade result set simply reports that it completed, and
 		the number of rows deleted.  It does not return rows.
 		The delete has been completed once the

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/GenericActivationHolder.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/GenericActivationHolder.java?rev=1535360&r1=1535359&r2=1535360&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/GenericActivationHolder.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/GenericActivationHolder.java Thu Oct 24 12:41:59 2013
@@ -772,6 +772,16 @@ final public class GenericActivationHold
 		return ac.isCursorActivation();
 	}
 
+    public  ConstantAction    pushConstantAction( ConstantAction newConstantAction )
+    {
+        return ac.pushConstantAction( newConstantAction );
+    }
+
+    public  ConstantAction    popConstantAction()
+    {
+        return ac.popConstantAction();
+    }
+
 	public ConstantAction getConstantAction() {
 		return ac.getConstantAction();
 	}

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/CurrentOfNode.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/CurrentOfNode.java?rev=1535360&r1=1535359&r2=1535360&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/CurrentOfNode.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/CurrentOfNode.java Thu Oct 24 12:41:59 2013
@@ -69,6 +69,9 @@ public final class CurrentOfNode extends
 	private TableName 				baseTableName;
 	private CostEstimate 			singleScanCostEstimate;
 
+    // dummy variables for compiling a CurrentOfNode in the DELETE action of a MERGE statement
+    private FromBaseTable       dummyTargetTable;
+
 	//
 	// initializers
 	//
@@ -81,6 +84,25 @@ public final class CurrentOfNode extends
         cursorName = cursor;
 	}
 
+    /**
+     * <p>
+     * Construct a dummy CurrentOfNode just for compiling the DELETE action of a MERGE
+     * statement.
+     * </p>
+     */
+    static  CurrentOfNode   makeForMerge
+        (
+         String cursorName,
+         FromBaseTable  dummyTargetTable,
+         ContextManager cm
+         )
+    {
+        CurrentOfNode   node = new CurrentOfNode( null, cursorName, null, cm );
+        node.dummyTargetTable = dummyTargetTable;
+
+        return node;
+    }
+
 	/*
 	 * Optimizable interface
 	 */
@@ -264,6 +286,10 @@ public final class CurrentOfNode extends
     ResultColumn getMatchingColumn(ColumnReference columnReference)
 						throws StandardException {
 
+        // if this is a dummy CurrentOfNode cooked up to compile a DELETE action
+        // of a MERGE statement, then short-circuit the matching column lookup
+        if ( dummyTargetTable != null ) { return dummyTargetTable.getMatchingColumn( columnReference ); }
+
 		ResultColumn	resultColumn = null;
 		TableName		columnsTableName;
 
@@ -511,6 +537,10 @@ public final class CurrentOfNode extends
     @Override
     String  getExposedName()
 	{
+        // short-circuit for dummy CurrentOfNode cooked up to support
+        // the DELETE action of a MERGE statement
+        if ( dummyTargetTable != null ) { return dummyTargetTable.getExposedName(); }
+        
 		return exposedTableName.getFullTableName();
 	}
 

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/DMLModStatementNode.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/DMLModStatementNode.java?rev=1535360&r1=1535359&r2=1535360&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/DMLModStatementNode.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/DMLModStatementNode.java Thu Oct 24 12:41:59 2013
@@ -103,12 +103,21 @@ abstract class DMLModStatementNode exten
 	protected  boolean isDependentTable;
 	protected int[][] fkColArrays; 
 	protected TableName synonymTableName;
+    protected   MatchingClauseNode  matchingClause;
+
 
     /** Set of dependent tables for cascading deletes. */
     Set<String> dependentTables;
 
-    DMLModStatementNode(ResultSetNode resultSet, ContextManager cm) {
+    DMLModStatementNode
+        (
+         ResultSetNode resultSet,
+         MatchingClauseNode matchingClause,
+         ContextManager cm
+         )
+    {
         super(resultSet, cm);
+        this.matchingClause = matchingClause;
         statementType = getStatementType();
     }
 
@@ -117,18 +126,27 @@ abstract class DMLModStatementNode exten
 	 *
 	 * @param resultSet	A ResultSetNode for the result set of the
 	 *			DML statement
+     * @param matchingClause   Non-null if this DML is part of a MATCHED clause of a MERGE statement.
 	 * @param statementType used by nodes that allocate a DMLMod directly
 	 *			(rather than inheriting it).
      * @param cm        The context manager
 	 */
-    DMLModStatementNode(ResultSetNode resultSet,
-                        int statementType,
-                        ContextManager cm)
+    DMLModStatementNode
+        (
+         ResultSetNode resultSet,
+         MatchingClauseNode matchingClause,
+         int statementType,
+         ContextManager cm
+         )
 	{
         super(resultSet, cm);
+        this.matchingClause = matchingClause;
         this.statementType = statementType;
 	}
 
+    /** Returns true if this DMLModStatement a [ NOT ] MATCHED action of a MERGE statement */
+    public  boolean inMatchingClause() { return matchingClause != null; }
+
 	void setTarget(QueryTreeNode targetName)
 	{
 		if (targetName instanceof TableName)
@@ -1624,8 +1642,17 @@ abstract class DMLModStatementNode exten
     @Override
 	public void optimizeStatement() throws StandardException
 	{
-		/* First optimize the query */
-		super.optimizeStatement();
+        //
+        // If this is the INSERT/UPDATE/DELETE action of a MERGE statement,
+        // then we don't need to optimize the dummy driving result set, which
+        // is never actually run.
+        //
+        // don't need to optimize the dummy SELECT, which is never actually run
+        if ( !inMatchingClause() )
+        {
+            /* First optimize the query */
+            super.optimizeStatement();
+        }
 
 		/* In language we always set it to row lock, it's up to store to
 		 * upgrade it to table lock.  This makes sense for the default read

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/DeleteNode.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/DeleteNode.java?rev=1535360&r1=1535359&r2=1535360&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/DeleteNode.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/DeleteNode.java Thu Oct 24 12:41:59 2013
@@ -84,13 +84,19 @@ class DeleteNode extends DMLModStatement
 	 * @param targetTableName	The name of the table to delete from
 	 * @param queryExpression	The query expression that will generate
 	 *				the rows to delete from the given table
+     * @param matchingClause   Non-null if this DML is part of a MATCHED clause of a MERGE statement.
      * @param cm                The context manager
 	 */
 
-    DeleteNode(TableName targetTableName,
-               ResultSetNode queryExpression,
-               ContextManager cm) {
-        super(queryExpression, cm);
+    DeleteNode
+        (
+         TableName targetTableName,
+         ResultSetNode queryExpression,
+         MatchingClauseNode matchingClause,
+         ContextManager cm
+         )
+    {
+        super( queryExpression, matchingClause, cm );
         this.targetTableName = targetTableName;
 	}
 
@@ -130,9 +136,10 @@ class DeleteNode extends DMLModStatement
 			CurrentRowLocationNode		rowLocationNode;
 			TableName					cursorTargetTableName = null;
 			CurrentOfNode       		currentOfNode = null;
-		
+
 			DataDictionary dataDictionary = getDataDictionary();
-			super.bindTables(dataDictionary);
+            // for DELETE clause of a MERGE statement, the tables have already been bound
+			if ( !inMatchingClause() ) { super.bindTables(dataDictionary); }
 
 			// wait to bind named target table until the underlying
 			// cursor is bound, so that we can get it from the
@@ -149,7 +156,8 @@ class DeleteNode extends DMLModStatement
 			{
 				currentOfNode = (CurrentOfNode) targetTable;
 
-				cursorTargetTableName = currentOfNode.getBaseCursorTargetTableName();
+				cursorTargetTableName = inMatchingClause() ?
+                    targetTableName : currentOfNode.getBaseCursorTargetTableName();
 				// instead of an assert, we might say the cursor is not updatable.
 				if (SanityManager.DEBUG)
 					SanityManager.ASSERT(cursorTargetTableName != null);
@@ -191,9 +199,11 @@ class DeleteNode extends DMLModStatement
 			verifyTargetTable();
 
 			/* Generate a select list for the ResultSetNode - CurrentRowLocation(). */
-			if (SanityManager.DEBUG)
+			if ( SanityManager.DEBUG )
+            {
 				SanityManager.ASSERT((resultSet.resultColumns == null),
 							  "resultColumns is expected to be null until bind time");
+            }
 
 
 			if (targetTable instanceof FromVTI)
@@ -207,6 +217,7 @@ class DeleteNode extends DMLModStatement
 			}
 			else
 			{
+            
 				/*
 				** Start off assuming no columns from the base table
 				** are needed in the rcl.
@@ -256,7 +267,13 @@ class DeleteNode extends DMLModStatement
 				/* Force the added columns to take on the table's correlation name, if any */
 				correlateAddedColumns( resultColumnList, targetTable );
 			
-				/* Set the new result column list in the result set */
+                /* Add the new result columns to the driving result set */
+                ResultColumnList    originalRCL = resultSet.resultColumns;
+                if ( originalRCL != null )
+                {
+                    originalRCL.appendResultColumns( resultColumnList, false );
+                    resultColumnList = originalRCL;
+                }
 				resultSet.setResultColumns(resultColumnList);
 			}
 
@@ -490,7 +507,6 @@ class DeleteNode extends DMLModStatement
     void generate(ActivationClassBuilder acb, MethodBuilder mb)
 							throws StandardException
 	{
-
 		// If the DML is on the temporary table, generate the code to
 		// mark temporary table as modified in the current UOW. After
 		// DERBY-827 this must be done in execute() since
@@ -503,7 +519,16 @@ class DeleteNode extends DMLModStatement
 
 		acb.pushGetResultSetFactoryExpression(mb); 
 		acb.newRowLocationScanResultSetName();
-		resultSet.generate(acb, mb); // arg 1
+
+        // arg 1
+        if ( inMatchingClause() )
+        {
+            matchingClause.generateResultSetField( acb, mb );
+        }
+        else
+        {
+            resultSet.generate( acb, mb );
+        }
 
 		String resultSetGetter;
 		int argCount;
@@ -565,7 +590,6 @@ class DeleteNode extends DMLModStatement
 			mb.setField(arrayField);
 			for(int index=0 ; index <  dependentNodes.length ; index++)
 			{
-
 				dependentNodes[index].setRefActionInfo(fkIndexConglomNumbers[index],
 													   fkColArrays[index],
 													   parentResultSetId,
@@ -728,7 +752,6 @@ class DeleteNode extends DMLModStatement
     private DeleteNode getEmptyDeleteNode(String schemaName, String targetTableName)
         throws StandardException
     {
-
         ValueNode whereClause = null;
 
         TableName tableName =
@@ -759,8 +782,7 @@ class DeleteNode extends DMLModStatement
                                        null, /* optimizer override plan */
                                        getContextManager());
 
-        return new DeleteNode(tableName, rs, getContextManager());
-
+        return new DeleteNode(tableName, rs, null, getContextManager());
     }
 
 
@@ -803,8 +825,7 @@ class DeleteNode extends DMLModStatement
                                               null, /* optimizer override plan */
                                               getContextManager());
 
-        return new UpdateNode(tableName, sn, false, getContextManager());
-
+        return new UpdateNode(tableName, sn, null, getContextManager());
     }
 
 
@@ -845,7 +866,7 @@ class DeleteNode extends DMLModStatement
 			}
 		}
 
-		super.optimizeStatement();
+        super.optimizeStatement();
 	}
 
     /**

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/FromBaseTable.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/FromBaseTable.java?rev=1535360&r1=1535359&r2=1535360&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/FromBaseTable.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/FromBaseTable.java Thu Oct 24 12:41:59 2013
@@ -191,6 +191,9 @@ class FromBaseTable extends FromTable
     // true if we are running with sql authorization and this is the SYSUSERS table
     private boolean authorizeSYSUSERS;
 
+    // non-null if we need to return a row location column
+    private String  rowLocationColumnName;
+
 	/**
      * Constructor for a table in a FROM list. Parameters are as follows:
 	 *
@@ -236,6 +239,12 @@ class FromBaseTable extends FromTable
 		templateColumns = resultColumns;
 	}
 
+    /** Set the name of the row location column */
+    void    setRowLocationColumnName( String rowLocationColumnName )
+    {
+        this.rowLocationColumnName = rowLocationColumnName;
+    }
+
     /**
 	 * no LOJ reordering for base table.
 	 */
@@ -2794,7 +2803,11 @@ class FromBaseTable extends FromTable
                 columnReference.setColumnNumber(
                     resultColumn.getColumnPosition());
 
-				if (tableDescriptor != null)
+                // set the column-referenced bit if this is not the row location column
+				if (
+                    (tableDescriptor != null) &&
+                    ( (rowLocationColumnName == null) || !(rowLocationColumnName.equals( columnReference.getColumnName() )) )
+                    )
 				{
 					FormatableBitSet referencedColumnMap = tableDescriptor.getReferencedColumnMap();
 					if (referencedColumnMap == null)
@@ -3324,7 +3337,12 @@ class FromBaseTable extends FromTable
     @Override
     void generate(ActivationClassBuilder acb, MethodBuilder mb)
 							throws StandardException
-	{        
+	{
+        if ( rowLocationColumnName != null )
+        {
+            resultColumns.conglomerateId = tableDescriptor.getHeapConglomerateId();
+        }
+
 		generateResultSet( acb, mb );
 
 		/*
@@ -3874,6 +3892,18 @@ class FromBaseTable extends FromTable
 			rcList.addResultColumn(resultColumn);
 		}
 
+        // add a row location column as necessary
+        if ( rowLocationColumnName != null )
+        {
+            CurrentRowLocationNode  rowLocationNode = new CurrentRowLocationNode( getContextManager() );
+            ResultColumn    rowLocationColumn = new ResultColumn
+                ( rowLocationColumnName, rowLocationNode, getContextManager() );
+            rowLocationColumn.markGenerated();
+            rowLocationNode.bindExpression( null, null, null );
+            rowLocationColumn.bindResultColumnToExpression();
+            rcList.addResultColumn( rowLocationColumn );
+        }
+
 		return rcList;
 	}
 

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/InsertNode.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/InsertNode.java?rev=1535360&r1=1535359&r2=1535360&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/InsertNode.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/InsertNode.java Thu Oct 24 12:41:59 2013
@@ -98,6 +98,7 @@ public final class InsertNode extends DM
 	 *			it out.
      * @param queryExpression    The query expression that will generate
      *                           the rows to insert into the given table
+     * @param matchingClause   Non-null if this DML is part of a MATCHED clause of a MERGE statement.
      * @param targetProperties   The properties specified on the target table
      * @param orderByList        The order by list for the source result set,
      *                           null if no order by list
@@ -112,6 +113,7 @@ public final class InsertNode extends DM
             QueryTreeNode    targetName,
             ResultColumnList insertColumns,
             ResultSetNode    queryExpression,
+            MatchingClauseNode matchingClause,
             Properties       targetProperties,
             OrderByList      orderByList,
             ValueNode        offset,
@@ -123,7 +125,7 @@ public final class InsertNode extends DM
 		 * any properties, so we've kludged the code to get the
 		 * right statementType for a bulk insert replace.
 		 */
-        super(queryExpression, getStatementType(targetProperties), cm);
+        super(queryExpression, matchingClause, getStatementType(targetProperties), cm);
         setTarget(targetName);
         targetColumnList = insertColumns;
         this.targetProperties = targetProperties;

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/MatchingClauseNode.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/MatchingClauseNode.java?rev=1535360&r1=1535359&r2=1535360&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/MatchingClauseNode.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/MatchingClauseNode.java Thu Oct 24 12:41:59 2013
@@ -21,11 +21,21 @@
 
 package	org.apache.derby.impl.sql.compile;
 
+import java.lang.reflect.Modifier;
 import java.util.ArrayList;
+import java.util.HashMap;
 import org.apache.derby.iapi.error.StandardException;
+import org.apache.derby.iapi.reference.ClassName;
 import org.apache.derby.iapi.reference.SQLState;
+import org.apache.derby.iapi.services.compiler.LocalField;
+import org.apache.derby.iapi.services.compiler.MethodBuilder;
 import org.apache.derby.iapi.services.context.ContextManager;
+import org.apache.derby.iapi.services.io.FormatableBitSet;
 import org.apache.derby.shared.common.sanity.SanityManager;
+import org.apache.derby.iapi.sql.compile.CompilerContext;
+import org.apache.derby.iapi.sql.compile.Visitor;
+import org.apache.derby.iapi.sql.dictionary.DataDictionary;
+import org.apache.derby.iapi.sql.execute.ConstantAction;
 
 /**
  * Node representing a WHEN MATCHED or WHEN NOT MATCHED clause
@@ -41,6 +51,8 @@ public class MatchingClauseNode extends 
     //
     ///////////////////////////////////////////////////////////////////////////////////
 
+    private static  final   String  CURRENT_OF_NODE_NAME = "$MERGE_CURRENT";
+
     ///////////////////////////////////////////////////////////////////////////////////
     //
     // STATE
@@ -53,9 +65,22 @@ public class MatchingClauseNode extends 
     private ResultColumnList    _insertColumns;
     private ResultColumnList    _insertValues;
 
+    //
     // filled in at bind() time
+    //
+
+    /** the INSERT/UPDATE/DELETE statement of this WHEN [ NOT ] MATCHED clause */
     private DMLModStatementNode _dml;
 
+    /** the columns in the temporary conglomerate which drives the INSERT/UPDATE/DELETE */
+    private ResultColumnList        _thenColumns;
+    private int[]                           _thenColumnOffsets;
+
+    // Filled in at generate() time
+    private int                             _clauseNumber;
+    private String                          _actionMethodName;
+    private String                          _resultSetFieldName;
+    
     ///////////////////////////////////////////////////////////////////////////////////
     //
     // CONSTRUCTORS/FACTORY METHODS
@@ -83,7 +108,7 @@ public class MatchingClauseNode extends 
     }
 
     /** Make a WHEN MATCHED ... THEN UPDATE clause */
-    public  static  MatchingClauseNode   makeUpdateClause
+    static  MatchingClauseNode   makeUpdateClause
         (
          ValueNode  matchingRefinement,
          ResultColumnList   updateColumns,
@@ -94,7 +119,7 @@ public class MatchingClauseNode extends 
     }
 
     /** Make a WHEN MATCHED ... THEN DELETE clause */
-    public  static  MatchingClauseNode   makeDeleteClause
+    static  MatchingClauseNode   makeDeleteClause
         (
          ValueNode  matchingRefinement,
          ContextManager     cm
@@ -104,7 +129,7 @@ public class MatchingClauseNode extends 
     }
 
     /** Make a WHEN NOT MATCHED ... THEN INSERT clause */
-    public  static  MatchingClauseNode   makeInsertClause
+    static  MatchingClauseNode   makeInsertClause
         (
          ValueNode  matchingRefinement,
          ResultColumnList   insertColumns,
@@ -122,17 +147,23 @@ public class MatchingClauseNode extends 
     ///////////////////////////////////////////////////////////////////////////////////
 
     /** Return true if this is a WHEN MATCHED ... UPDATE clause */
-    public  boolean isUpdateClause()    { return (_updateColumns != null); }
+    boolean isUpdateClause()    { return (_updateColumns != null); }
     
     /** Return true if this is a WHEN NOT MATCHED ... INSERT clause */
-    public  boolean isInsertClause()    { return (_insertValues != null); }
+    boolean isInsertClause()    { return (_insertValues != null); }
     
     /** Return true if this is a WHEN MATCHED ... DELETE clause */
-    public  boolean isDeleteClause()    { return !( isUpdateClause() || isInsertClause() ); }
+    boolean isDeleteClause()    { return !( isUpdateClause() || isInsertClause() ); }
 
     /** Return the bound DML statement--returns null if called before binding */
-    public  DMLModStatementNode getDML()    { return _dml; }
-    
+    DMLModStatementNode getDML()    { return _dml; }
+
+    /**
+     * Return the list of columns which form the rows of the ResultSet which drive
+     * the INSERT/UPDATE/DELETE actions.
+     */
+    ResultColumnList    getBufferedColumns() { return _thenColumns; }
+
     ///////////////////////////////////////////////////////////////////////////////////
     //
     // bind() BEHAVIOR
@@ -140,33 +171,103 @@ public class MatchingClauseNode extends 
     ///////////////////////////////////////////////////////////////////////////////////
 
     /** Bind this WHEN [ NOT ] MATCHED clause against the parent JoinNode */
-    public void    bind( JoinNode joinNode, FromTable targetTable )
+    void    bind
+        (
+         DataDictionary dd,
+         MergeNode mergeNode,
+         FromList fullFromList,
+         FromBaseTable targetTable
+         )
         throws StandardException
     {
-        String  clauseType = isInsertClause() ? "WHEN NOT MATCHED" : "WHEN MATCHED";
+        bindExpressions( mergeNode, fullFromList, targetTable );
+        
+        if ( isDeleteClause() ) { bindDelete( dd, fullFromList, targetTable ); }
+        if ( isUpdateClause() ) { bindUpdate( dd, fullFromList, targetTable ); }
+        if ( isInsertClause() ) { bindInsert( dd, mergeNode, fullFromList, targetTable ); }
 
-        // For WHEN NOT MATCHED clauses, the target table is not in scope.
-        boolean useTargetTable = !isInsertClause();
+        bindExpressions( _thenColumns, fullFromList );
+    }
 
+    /** Bind the optional refinement condition in the MATCHED clause */
+    void    bindRefinement( MergeNode mergeNode, FromList fullFromList ) throws StandardException
+    {
         if ( _matchingRefinement != null )
         {
-            _matchingRefinement = joinNode.bindExpression
-                ( _matchingRefinement, true, useTargetTable, clauseType );
+            mergeNode.bindExpression( _matchingRefinement, fullFromList );
         }
+    }
 
-        if ( isDeleteClause() ) { bindDelete( joinNode, targetTable ); }
-        if ( isUpdateClause() ) { bindUpdate( joinNode, targetTable ); }
-        if ( isInsertClause() ) { bindInsert( joinNode, targetTable ); }
+    /** Bind the expressions in this MATCHED clause */
+    private void    bindExpressions
+        (
+         MergeNode mergeNode,
+         FromList fullFromList,
+         FromTable targetTable
+         )
+        throws StandardException
+    {
+        _thenColumns = new ResultColumnList( getContextManager() );
+
+        if ( isUpdateClause() )
+        {
+            // needed to make the UpdateNode bind
+            _updateColumns.replaceOrForbidDefaults( targetTable.getTableDescriptor(), _updateColumns, true );
+
+            bindExpressions( _updateColumns, fullFromList );
+        }
+        else if ( isInsertClause() )
+        {
+            // needed to make the SelectNode bind
+            _insertValues.replaceOrForbidDefaults( targetTable.getTableDescriptor(), _insertColumns, true );
+            bindExpressions( _insertValues, fullFromList );
+        }
     }
+    
+    /** Collect the columns mentioned by expressions in this MATCHED clause */
+    void    getColumnsInExpressions
+        (
+         MergeNode  mergeNode,
+         HashMap<String,ColumnReference> drivingColumnMap
+         )
+        throws StandardException
+    {
+        if ( _matchingRefinement != null )
+        {
+            mergeNode.getColumnsInExpression( drivingColumnMap, _matchingRefinement );
+        }
 
+        if ( isUpdateClause() )
+        {
+            // get all columns mentioned on the right side of SET operators in WHEN MATCHED ... THEN UPDATE clauses
+            for ( ResultColumn rc : _updateColumns )
+            {
+                mergeNode.getColumnsInExpression( drivingColumnMap, rc.getExpression() );
+            }
+        }
+        else if ( isInsertClause() )
+        {
+            // get all columns mentioned in the VALUES subclauses of WHEN NOT MATCHED ... THEN INSERT clauses
+            for ( ResultColumn rc : _insertValues )
+            {
+                mergeNode.getColumnsInExpression( drivingColumnMap, rc.getExpression() );
+            }
+        }
+    }
+    
     /** Bind a WHEN MATCHED ... THEN UPDATE clause */
-    private void    bindUpdate( JoinNode joinNode, FromTable targetTable )
+    private void    bindUpdate
+        (
+         DataDictionary dd,
+         FromList fullFromList,
+         FromTable targetTable
+         )
         throws StandardException
     {
         SelectNode  selectNode = new SelectNode
             (
              _updateColumns,
-             joinNode.makeFromList( true, true ),
+             fullFromList,
              null,      // where clause
              null,      // group by list
              null,      // having clause
@@ -174,50 +275,82 @@ public class MatchingClauseNode extends 
              null,      // optimizer plan override
              getContextManager()
              );
-        _dml = new UpdateNode( targetTable.getTableName(), selectNode, true, getContextManager() );
+        _dml = new UpdateNode( targetTable.getTableName(), selectNode, this, getContextManager() );
 
         _dml.bindStatement();
     }
     
     /** Bind a WHEN MATCHED ... THEN DELETE clause */
-    private void    bindDelete( JoinNode joinNode, FromTable targetTable )
+    private void    bindDelete
+        (
+         DataDictionary dd,
+         FromList fullFromList,
+         FromBaseTable targetTable
+         )
         throws StandardException
     {
-        SelectNode  selectNode = new SelectNode
+        CurrentOfNode   currentOfNode = CurrentOfNode.makeForMerge
+            ( CURRENT_OF_NODE_NAME, targetTable, getContextManager() );
+        FromList        fromList = new FromList( getContextManager() );
+        fromList.addFromTable( currentOfNode );
+        SelectNode      selectNode = new SelectNode
             (
-             null,      // select list
-             joinNode.makeFromList( true, true ),
-             null,      // where clause
-             null,      // group by list
-             null,      // having clause
-             null,      // window list
-             null,      // optimizer plan override
+             null,
+             fromList, /* FROM list */
+             null, /* WHERE clause */
+             null, /* GROUP BY list */
+             null, /* having clause */
+             null, /* window list */
+             null, /* optimizer plan override */
              getContextManager()
              );
-        _dml = new DeleteNode( targetTable.getTableName(), selectNode, getContextManager() );
+        _dml = new DeleteNode( targetTable.getTableName(), selectNode, this, getContextManager() );
 
         _dml.bindStatement();
+
+        ResultColumnList    deleteSignature = _dml.resultSet.resultColumns;
+        for ( int i = 0; i < deleteSignature.size(); i++ )
+        {
+            ResultColumn    origRC = deleteSignature.elementAt( i );
+            ResultColumn    newRC;
+            ValueNode       expression = origRC.getExpression();
+
+            if ( expression instanceof ColumnReference )
+            {
+                ColumnReference cr = (ColumnReference) ((ColumnReference) expression).getClone();
+                newRC = new ResultColumn( cr, cr, getContextManager() );
+            }
+            else
+            {
+                newRC = origRC.cloneMe();
+            }
+            _thenColumns.addResultColumn( newRC );
+        }
     }
 
     /** Bind a WHEN NOT MATCHED ... THEN INSERT clause */
-    private void    bindInsert( JoinNode joinNode, FromTable targetTable )
+    private void    bindInsert
+        (
+         DataDictionary dd,
+         MergeNode  mergeNode,
+         FromList fullFromList,
+         FromTable targetTable
+         )
         throws StandardException
     {
-        // needed to make the SelectNode bind
-        _insertValues.replaceOrForbidDefaults( targetTable.getTableDescriptor(), _insertColumns, true );
-
         // the VALUES clause may not mention columns in the target table
-        _insertValues.bindExpressions
-            (
-             joinNode.makeFromList( true, false ),
-             new SubqueryList( getContextManager() ),
-             new ArrayList<AggregateNode>()
-             );
+        FromList    targetTableFromList = new FromList( getOptimizerFactory().doJoinOrderOptimization(), getContextManager() );
+        targetTableFromList.addElement( fullFromList.elementAt( 0 ) );
+        bindExpressions( _insertValues, targetTableFromList );
+        if ( _matchingRefinement != null )
+        {
+            mergeNode.bindExpression( _matchingRefinement, targetTableFromList );
+        }
         
         SelectNode  selectNode = new SelectNode
             (
              _insertValues,      // select list
-             joinNode.makeFromList( true, true ),
+             fullFromList,
              null,      // where clause
              null,      // group by list
              null,      // having clause
@@ -230,6 +363,7 @@ public class MatchingClauseNode extends 
              targetTable.getTableName(),
              _insertColumns,
              selectNode,
+             this,      // in NOT MATCHED clause
              null,      // targetProperties
              null,      // order by cols
              null,      // offset
@@ -241,4 +375,243 @@ public class MatchingClauseNode extends 
         _dml.bindStatement();
     }
 
+    /** Boilerplate for binding a list of ResultColumns against a FromList */
+    private void bindExpressions( ResultColumnList rcl, FromList fromList )
+        throws StandardException
+    {
+        CompilerContext cc = getCompilerContext();
+        final int previousReliability = cc.getReliability();
+        
+        try {
+            cc.setReliability( previousReliability | CompilerContext.SQL_IN_ROUTINES_ILLEGAL );
+            
+            rcl.bindExpressions
+                (
+                 fromList,
+                 new SubqueryList( getContextManager() ),
+                 new ArrayList<AggregateNode>()
+                 );
+        }
+        finally
+        {
+            // Restore previous compiler state
+            cc.setReliability( previousReliability );
+        }
+    }
+
+    /**
+     * <p>
+     * Calculate the 1-based offsets which define the rows which will be buffered up
+     * for this INSERT/UPDATE/DELETE action at run-time. The rows are constructed
+     * from the columns in the SELECT list of the driving left joins. This method
+     * calculates an array of offsets into the SELECT list. The columns at those
+     * offsets will form the row which is buffered up for the INSERT/UPDATE/DELETE
+     * action.
+     * </p>
+     */
+    void    bindThenColumns( ResultColumnList selectList )
+        throws StandardException
+    {
+        int     bufferedCount = _thenColumns.size();
+        int     selectCount = selectList.size();
+        
+        _thenColumnOffsets = new int[ bufferedCount ];
+
+        for ( int bidx = 0; bidx < bufferedCount; bidx++ )
+        {
+            ResultColumn    bufferedRC = _thenColumns.elementAt( bidx );
+            ValueNode       bufferedExpression = bufferedRC.getExpression();
+            int                     offset = -1;    // start out undefined
+
+            if ( bufferedExpression instanceof ColumnReference )
+            {
+                ColumnReference bufferedCR = (ColumnReference) bufferedExpression;
+                String              tableName = bufferedCR.getTableName();
+                String              columnName = bufferedCR.getColumnName();
+
+                // loop through the SELECT list to find this column reference
+                for ( int sidx = 0; sidx < selectCount; sidx++ )
+                {
+                    ResultColumn    selectRC = selectList.elementAt( sidx );
+                    ValueNode       selectExpression = selectRC.getExpression();
+                    ColumnReference selectCR = selectExpression instanceof ColumnReference ?
+                        (ColumnReference) selectExpression : null;
+
+                    if ( selectCR != null )
+                    {
+                        if (
+                            tableName.equals( selectCR.getTableName() ) &&
+                            columnName.equals( selectCR.getColumnName() )
+                            )
+                        {
+                            offset = sidx + 1;
+                            break;
+                        }
+                    }
+                }
+            }
+            else if ( bufferedExpression instanceof CurrentRowLocationNode )
+            {
+                //
+                // There is only one RowLocation in the SELECT list, the row location for the
+                // tuple from the target table. The RowLocation is always the last column in
+                // the SELECT list.
+                //
+                offset = selectCount;
+            }
+
+            _thenColumnOffsets[ bidx ] = offset;
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // optimize() BEHAVIOR
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * <p>
+     * Optimize the INSERT/UPDATE/DELETE action.
+     * </p>
+     */
+    void    optimize()  throws StandardException
+    {
+        _dml.optimizeStatement();
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // generate() BEHAVIOR
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    ConstantAction makeConstantAction( ActivationClassBuilder acb )
+        throws StandardException
+	{
+        // generate the clause-specific refinement
+        String  refinementName = null;
+        if ( _matchingRefinement != null )
+        {
+            MethodBuilder userExprFun = acb.newUserExprFun();
+
+            _matchingRefinement.generateExpression( acb, userExprFun );
+            userExprFun.methodReturn();
+		
+            // we are done modifying userExprFun, complete it.
+            userExprFun.complete();
+
+            refinementName = userExprFun.getName();
+        }
+        
+        return	getGenericConstantActionFactory().getMatchingClauseConstantAction
+            (
+             getClauseType(),
+             refinementName,
+             _thenColumnOffsets,
+             _resultSetFieldName,
+             _actionMethodName,
+             _dml.makeConstantAction()
+             );
+	}
+    private int getClauseType()
+    {
+        if ( isUpdateClause() ) { return ConstantAction.WHEN_MATCHED_THEN_UPDATE; }
+        else if ( isInsertClause() ) { return ConstantAction.WHEN_NOT_MATCHED_THEN_INSERT; }
+        else { return ConstantAction.WHEN_MATCHED_THEN_DELETE; }
+    }
+
+    /**
+     * <p>
+     * Generate a method to invoke the INSERT/UPDATE/DELETE action. This method
+     * will be called at runtime by MatchingClauseConstantAction.executeConstantAction().
+     * </p>
+     */
+    void    generate( ActivationClassBuilder acb, int clauseNumber )
+        throws StandardException
+    {
+        _clauseNumber = clauseNumber;
+        _actionMethodName = "mergeActionMethod_" + _clauseNumber;
+        
+        MethodBuilder mb = acb.getClassBuilder().newMethodBuilder
+            (
+             Modifier.PUBLIC,
+             ClassName.ResultSet,
+             _actionMethodName
+             );
+        mb.addThrownException(ClassName.StandardException);
+
+        // now generate the action into this method
+        _dml.generate( acb, mb );
+        
+        mb.methodReturn();
+        mb.complete();
+    }
+
+    /**
+     * <p>
+     * Adds a field to the generated class to hold the ResultSet of buffered rows
+     * which drive the INSERT/UPDATE/DELETE action. Generates code to push
+     * the contents of that field onto the stack.
+     * </p>
+     */
+    void    generateResultSetField( ActivationClassBuilder acb, MethodBuilder mb )
+        throws StandardException
+    {
+        _resultSetFieldName = "mergeResultSetField_" + _clauseNumber;
+        
+        // make the field public so we can stuff it at execution time
+        LocalField  resultSetField = acb.newFieldDeclaration( Modifier.PUBLIC, ClassName.NoPutResultSet, _resultSetFieldName );
+
+        //
+        // At runtime, MatchingClauseConstantAction.executeConstantAction()
+        // will stuff the resultSetField with the temporary table which collects
+        // the rows relevant to this action. We want to push the value of resultSetField
+        // onto the stack, where it will be the ResultSet argument to the constructor
+        // of the actual INSERT/UPDATE/DELETE action.
+        //
+        mb.getField( resultSetField );
+    }
+    
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // Visitable BEHAVIOR
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+	/**
+	 * Accept the visitor for all visitable children of this node.
+	 * 
+	 * @param v the visitor
+	 *
+	 * @exception StandardException on error
+	 */
+    @Override
+	void acceptChildren(Visitor v)
+		throws StandardException
+	{
+		super.acceptChildren( v );
+
+        if ( _matchingRefinement != null ) { _matchingRefinement.accept( v ); }
+        if ( _updateColumns != null ) { _updateColumns.accept( v ); }
+        if ( _insertColumns != null ) { _insertColumns.accept( v ); }
+        if ( _insertValues != null ) { _insertValues.accept( v ); }
+
+        if ( _dml != null ) { _dml.accept( v ); }
+	}
+
+	/**
+	 * Convert this object to a String.  See comments in QueryTreeNode.java
+	 * for how this should be done for tree printing.
+	 *
+	 * @return	This object as a String
+	 */
+    @Override
+	public String toString()
+	{
+        if ( isUpdateClause() ) { return "UPDATE"; }
+        else if ( isInsertClause() ) { return "INSERT"; }
+        else { return "DELETE"; }
+	}
+
 }

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/MergeNode.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/MergeNode.java?rev=1535360&r1=1535359&r2=1535360&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/MergeNode.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/MergeNode.java Thu Oct 24 12:41:59 2013
@@ -21,24 +21,105 @@
 
 package	org.apache.derby.impl.sql.compile;
 
+import java.util.Arrays;
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
 
 import org.apache.derby.iapi.error.StandardException;
+import org.apache.derby.iapi.reference.ClassName;
 import org.apache.derby.iapi.reference.SQLState;
+import org.apache.derby.iapi.services.classfile.VMOpcode;
+import org.apache.derby.iapi.services.compiler.MethodBuilder;
 import org.apache.derby.iapi.services.context.ContextManager;
+import org.apache.derby.iapi.sql.compile.CompilerContext;
+import org.apache.derby.iapi.sql.compile.Visitor;
+import org.apache.derby.iapi.sql.dictionary.ColumnDescriptor;
+import org.apache.derby.iapi.sql.dictionary.ColumnDescriptorList;
 import org.apache.derby.iapi.sql.dictionary.DataDictionary;
 import org.apache.derby.iapi.sql.dictionary.TableDescriptor;
+import org.apache.derby.iapi.sql.execute.ConstantAction;
+import org.apache.derby.iapi.util.IdUtil;
 
 /**
  * <p>
- * A MergeNode represents a MERGE statement.  It is the top node of the
- * query tree for that statement. The driving result set for a MERGE statement
- * is essentially the following:
+ * A MergeNode represents a MERGE statement. The statement looks like
+ * this...
  * </p>
  *
  * <pre>
- * sourceTable LEFT OUTER JOIN targetTable ON searchCondition
+ * MERGE INTO targetTable
+ * USING sourceTable
+ * ON searchCondition
+ * matchingClause1 ... matchingClauseN
  * </pre>
+ *
+ * <p>
+ * ...where each matching clause looks like this...
+ * </p>
+ *
+ * <pre>
+ * WHEN MATCHED [ AND matchingRefinement ] THEN DELETE
+ * </pre>
+ *
+ * <p>
+ * ...or
+ * </p>
+ *
+ * <pre>
+ * WHEN MATCHED [ AND matchingRefinement ] THEN UPDATE SET col1 = expr1, ... colM = exprM
+ * </pre>
+ *
+ * <p>
+ * ...or
+ * </p>
+ *
+ * <pre>
+ * WHEN NOT MATCHED [ AND matchingRefinement ] THEN INSERT columnList VALUES valueList
+ * </pre>
+ *
+ * <p>
+ * The Derby compiler essentially rewrites this statement into a driving left join
+ * followed by a series of DELETE/UPDATE/INSERT actions. The left join looks like
+ * this:
+ * </p>
+ *
+ * <pre>
+ * SELECT selectList FROM sourceTable LEFT OUTER JOIN targetTable ON searchCondition
+ * </pre>
+ *
+ * <p>
+ * The selectList of the driving left join consists of the following:
+ * </p>
+ *
+ * <ul>
+ * <li>All of the columns mentioned in the searchCondition.</li>
+ * <li>All of the columns mentioned in the matchingRefinement clauses.</li>
+ * <li>All of the columns mentioned in the SET clauses and the INSERT columnLists and valueLists.</li>
+ * <li>All additional columns needed for the triggers and foreign keys fired by the DeleteResultSets
+ * and UpdateResultSets constructed for the WHEN MATCHED clauses.</li>
+ * <li>All additional columns needed to build index rows and evaluate generated columns
+ * needed by the UpdateResultSets constructed for the WHEN MATCHED...THEN UPDATE clauses.</li>
+ * <li>A trailing targetTable.RowLocation column.</li>
+ * </ul>
+ *
+ * <p>
+ * The matchingRefinement expressions are bound and generated against the
+ * FromList of the driving left join. Dummy DeleteNode, UpdateNode, and InsertNode
+ * statements are independently constructed in order to bind and generate the DELETE/UPDATE/INSERT
+ * actions.
+ * </p>
+ *
+ * <p>
+ * At execution time, the targetTable.RowLocation column is used to determine
+ * whether a given driving row matches. The row matches iff targetTable.RowLocation is not null.
+ * The driving row is then assigned to the
+ * first DELETE/UPDATE/INSERT action to which it applies. The relevant columns from
+ * the driving row are extracted and buffered in a temporary table specific to that
+ * DELETE/UPDATE/INSERT action. After the driving left join has been processed,
+ * the DELETE/UPDATE/INSERT actions are run in order, each taking its corresponding
+ * temporary table as its source ResultSet.
+ * </p>
  */
 
 public final class MergeNode extends DMLModStatementNode
@@ -49,17 +130,30 @@ public final class MergeNode extends DML
     //
     ///////////////////////////////////////////////////////////////////////////////////
 
+    private static  final   int SOURCE_TABLE_INDEX = 0;
+    private static  final   int TARGET_TABLE_INDEX = 1;
+
+	private static final String TARGET_ROW_LOCATION_NAME = "###TargetRowLocation";
+
     ///////////////////////////////////////////////////////////////////////////////////
     //
     // STATE
     //
     ///////////////////////////////////////////////////////////////////////////////////
 
-    private FromTable   _targetTable;
+    // constructor args
+    private FromBaseTable   _targetTable;
     private FromTable   _sourceTable;
     private ValueNode   _searchCondition;
     private ArrayList<MatchingClauseNode>   _matchingClauses;
 
+    // filled in at bind() time
+    private FromList                _leftJoinFromList;
+
+    // filled in at generate() time
+    private ConstantAction      _constantAction;
+    private CursorNode          _leftJoinCursor;
+
     ///////////////////////////////////////////////////////////////////////////////////
     //
     // CONSTRUCTOR
@@ -81,33 +175,14 @@ public final class MergeNode extends DML
          )
         throws StandardException
     {
-        super( null, cm );
+        super( null, null, cm );
 
-        _targetTable = targetTable;
+        if ( !( targetTable instanceof FromBaseTable) ) { notBaseTable(); }
+        else { _targetTable = (FromBaseTable) targetTable; }
+        
         _sourceTable = sourceTable;
         _searchCondition = searchCondition;
         _matchingClauses = matchingClauses;
-
-        makeJoin();
-    }
-
-    /**
-     * <p>
-     * Construct the left outer join which will drive the execution.
-     * </p>
-     */
-    private void    makeJoin() throws StandardException
-    {
-        resultSet = new HalfOuterJoinNode
-            (
-             _sourceTable,
-             _targetTable,
-             _searchCondition,
-             null,
-             false,
-             null,
-             getContextManager()
-             );
     }
 
     ///////////////////////////////////////////////////////////////////////////////////
@@ -121,35 +196,50 @@ public final class MergeNode extends DML
 	{
         DataDictionary  dd = getDataDictionary();
 
-        //
-        // Bind the left join. This binds _targetTable and _sourceTable.
-        //
-        bind( dd );
-
-        bindSearchCondition();
-
-        if ( !targetIsBaseTable() )
+        FromList    dummyFromList = new FromList( getContextManager() );
+        FromBaseTable   dummyTargetTable = new FromBaseTable
+            (
+             _targetTable.tableName,
+             _targetTable.correlationName,
+             null,
+             null,
+             getContextManager()
+             );
+        FromTable       dummySourceTable = cloneSourceTable();
+        
+        // source and target may not have the same correlation names
+        if ( getExposedName( dummyTargetTable ).equals( getExposedName( dummySourceTable ) ) )
         {
-            throw StandardException.newException( SQLState.LANG_TARGET_NOT_BASE_TABLE );
+            throw StandardException.newException( SQLState.LANG_SAME_EXPOSED_NAME );
         }
 
-        if ( !sourceIsBase_View_or_VTI() )
+        dummyFromList.addFromTable( dummySourceTable );
+        dummyFromList.addFromTable( dummyTargetTable );
+        dummyFromList.bindTables( dd, new FromList( getOptimizerFactory().doJoinOrderOptimization(), getContextManager() ) );
+        
+        if ( !targetIsBaseTable( dummyTargetTable ) ) { notBaseTable(); }
+
+        for ( MatchingClauseNode mcn : _matchingClauses )
         {
-            throw StandardException.newException( SQLState.LANG_SOURCE_NOT_BASE_VIEW_OR_VTI );
+            mcn.bind( dd, this, dummyFromList, dummyTargetTable );
         }
+        
+        bindLeftJoin( dd );
 
-        // source and target may not have the same correlation names
-        if ( getExposedName( _targetTable ).equals( getExposedName( _sourceTable ) ) )
+        // re-bind the matchingRefinement clauses now that we have result set numbers
+        // from the driving left join.
+        for ( MatchingClauseNode mcn : _matchingClauses )
         {
-            throw StandardException.newException( SQLState.LANG_SAME_EXPOSED_NAME );
+            mcn.bindRefinement( this, _leftJoinFromList );
         }
-
+        
         for ( MatchingClauseNode mcn : _matchingClauses )
         {
-            mcn.bind( (JoinNode) resultSet, _targetTable );
+            if ( mcn.isUpdateClause() || mcn.isInsertClause() )
+            {
+                throw StandardException.newException( SQLState.NOT_IMPLEMENTED, "MERGE" );
+            }
         }
-
-        throw StandardException.newException( SQLState.NOT_IMPLEMENTED, "MERGE" );
 	}
 
     /** Get the exposed name of a FromTable */
@@ -158,21 +248,145 @@ public final class MergeNode extends DML
         return ft.getTableName().getTableName();
     }
 
-    /**  Bind the search condition, the ON clause of the left join */
-    private void    bindSearchCondition()   throws StandardException
+    /**
+     * Bind the driving left join select.
+     * Stuffs the left join SelectNode into the resultSet variable.
+     */
+    private void    bindLeftJoin( DataDictionary dd )   throws StandardException
     {
-        FromList    fromList = new FromList
-            ( getOptimizerFactory().doJoinOrderOptimization(), getContextManager() );
+        CompilerContext cc = getCompilerContext();
+        final int previousReliability = cc.getReliability();
+        
+        try {
+            cc.setReliability( previousReliability | CompilerContext.SQL_IN_ROUTINES_ILLEGAL );
+            
+            HalfOuterJoinNode   hojn = new HalfOuterJoinNode
+                (
+                 _sourceTable,
+                 _targetTable,
+                 _searchCondition,
+                 null,
+                 false,
+                 null,
+                 getContextManager()
+                 );
+
+            _leftJoinFromList = hojn.makeFromList( true, true );
+            _leftJoinFromList.bindTables( dd, new FromList( getOptimizerFactory().doJoinOrderOptimization(), getContextManager() ) );
+
+            if ( !sourceIsBase_View_or_VTI() )
+            {
+                throw StandardException.newException( SQLState.LANG_SOURCE_NOT_BASE_VIEW_OR_VTI );
+            }
+
+            FromList    topFromList = new FromList( getOptimizerFactory().doJoinOrderOptimization(), getContextManager() );
+            topFromList.addFromTable( hojn );
+
+            // preliminary binding of the matching clauses to resolve column
+            // referneces. this ensures that we can add all of the columns from
+            // the matching refinements to the SELECT list of the left join.
+            // we re-bind the matching clauses when we're done binding the left join
+            // because, at that time, we have result set numbers needed for
+            // code generation.
+            for ( MatchingClauseNode mcn : _matchingClauses )
+            {
+                mcn.bindRefinement( this, _leftJoinFromList );
+            }
+        
+            ResultColumnList    selectList = buildSelectList();
+
+            // calculate the offsets into the SELECT list which define the rows for
+            // the WHEN [ NOT ] MATCHED  actions
+            for ( MatchingClauseNode mcn : _matchingClauses )
+            {
+                mcn.bindThenColumns( selectList );
+            }            
+            
+            resultSet = new SelectNode
+                (
+                 selectList,
+                 topFromList,
+                 null,      // where clause
+                 null,      // group by list
+                 null,      // having clause
+                 null,      // window list
+                 null,      // optimizer plan override
+                 getContextManager()
+                 );
+
+            // Wrap the SELECT in a CursorNode in order to finish binding it.
+            _leftJoinCursor = new CursorNode
+                (
+                 "SELECT",
+                 resultSet,
+                 null,
+                 null,
+                 null,
+                 null,
+                 false,
+                 CursorNode.READ_ONLY,
+                 null,
+                 getContextManager()
+                 );
+            _leftJoinCursor.bindStatement();
+        }
+        finally
+        {
+            // Restore previous compiler state
+            cc.setReliability( previousReliability );
+        }
+    }
 
-        resultSet.bindResultColumns( fromList );
+    /** Throw a "not base table" exception */
+    private void    notBaseTable()  throws StandardException
+    {
+        throw StandardException.newException( SQLState.LANG_TARGET_NOT_BASE_TABLE );
     }
 
-    /** Return true if the target table is a base table */
-    private boolean targetIsBaseTable() throws StandardException
+    /** Build the select list for the left join */
+    private ResultColumnList    buildSelectList() throws StandardException
     {
-        if ( !( _targetTable instanceof FromBaseTable) ) { return false; }
+        HashMap<String,ColumnReference> drivingColumnMap = new HashMap<String,ColumnReference>();
+        getColumnsInExpression( drivingColumnMap, _searchCondition );
+        
+        for ( MatchingClauseNode mcn : _matchingClauses )
+        {
+            mcn.getColumnsInExpressions( this, drivingColumnMap );
+            getColumnsFromList( drivingColumnMap, mcn.getBufferedColumns() );
+        }
+
+        ResultColumnList    selectList = new ResultColumnList( getContextManager() );
+
+        // add all of the columns from the source table which are mentioned
+        addColumns( (FromTable) _leftJoinFromList.elementAt( SOURCE_TABLE_INDEX ), drivingColumnMap, selectList );
+        // add all of the columns from the target table which are mentioned
+        addColumns( (FromTable) _leftJoinFromList.elementAt( TARGET_TABLE_INDEX ), drivingColumnMap, selectList );
+
+        addTargetRowLocation( selectList );
+
+        return selectList;
+    }
 
-        FromBaseTable   fbt = (FromBaseTable) _targetTable;
+    /** Add the target table's row location to the left join's select list */
+    private void    addTargetRowLocation( ResultColumnList selectList )
+        throws StandardException
+    {
+        // tell the target table to generate a row location column
+        _targetTable.setRowLocationColumnName( TARGET_ROW_LOCATION_NAME );
+        
+        TableName   fromTableName = _targetTable.getTableName();
+        ColumnReference cr = new ColumnReference
+                ( TARGET_ROW_LOCATION_NAME, fromTableName, getContextManager() );
+        ResultColumn    rowLocationColumn = new ResultColumn( (String) null, cr, getContextManager() );
+        rowLocationColumn.markGenerated();
+
+        selectList.addResultColumn( rowLocationColumn );
+    }
+
+    /** Return true if the target table is a base table */
+    private boolean targetIsBaseTable( FromBaseTable targetTable ) throws StandardException
+    {
+        FromBaseTable   fbt = targetTable;
         TableDescriptor desc = fbt.getTableDescriptor();
         if ( desc == null ) { return false; }
 
@@ -200,4 +414,271 @@ public final class MergeNode extends DML
         }
     }
 
+    /** Clone the source table for binding the MATCHED clauses */
+    private FromTable   cloneSourceTable() throws StandardException
+    {
+        if ( _sourceTable instanceof FromVTI )
+        {
+            FromVTI source = (FromVTI) _sourceTable;
+
+            return new FromVTI
+                (
+                 source.methodCall,
+                 source.correlationName,
+                 source.resultColumns,
+                 null,
+                 source.exposedName,
+                 getContextManager()
+                 );
+        }
+        else if ( _sourceTable instanceof FromBaseTable )
+        {
+            FromBaseTable   source = (FromBaseTable) _sourceTable;
+            return new FromBaseTable
+                (
+                 source.tableName,
+                 source.correlationName,
+                 null,
+                 null,
+                 getContextManager()
+                 );
+        }
+        else
+        {
+            throw StandardException.newException( SQLState.LANG_SOURCE_NOT_BASE_VIEW_OR_VTI );
+        }
+    }
+
+    /**
+     * <p>
+     * Add to an evolving select list the columns from the indicated table.
+     * </p>
+     */
+    private void    addColumns
+        (
+         FromTable  fromTable,
+         HashMap<String,ColumnReference> drivingColumnMap,
+         ResultColumnList   selectList
+         )
+        throws StandardException
+    {
+        String[]    columnNames = getColumns( getExposedName( fromTable ), drivingColumnMap );
+        
+        for ( int i = 0; i < columnNames.length; i++ )
+        {
+            ColumnReference cr = new ColumnReference
+                ( columnNames[ i ], fromTable.getTableName(), getContextManager() );
+            ResultColumn    rc = new ResultColumn( (String) null, cr, getContextManager() );
+            selectList.addResultColumn( rc );
+        }
+    }
+
+    /** Get the column names from the table with the given table number, in sorted order */
+    private String[]    getColumns( String exposedName, HashMap<String,ColumnReference> map )
+    {
+        ArrayList<String>   list = new ArrayList<String>();
+
+        for ( ColumnReference cr : map.values() )
+        {
+            if ( exposedName.equals( cr.getTableName() ) ) { list.add( cr.getColumnName() ); }
+        }
+
+        String[]    retval = new String[ list.size() ];
+        list.toArray( retval );
+        Arrays.sort( retval );
+
+        return retval;
+    }
+    
+    /** Add the columns in the matchingRefinement clause to the evolving map */
+    void    getColumnsInExpression
+        ( HashMap<String,ColumnReference> map, ValueNode expression )
+        throws StandardException
+    {
+        if ( expression == null ) { return; }
+
+        CollectNodesVisitor<ColumnReference> getCRs =
+            new CollectNodesVisitor<ColumnReference>(ColumnReference.class);
+
+        expression.accept(getCRs);
+        List<ColumnReference> colRefs = getCRs.getList();
+
+        getColumnsFromList( map, colRefs );
+    }
+
+    /** Add a list of columns to the the evolving map */
+    private void    getColumnsFromList
+        ( HashMap<String,ColumnReference> map, ResultColumnList rcl )
+        throws StandardException
+    {
+        ArrayList<ColumnReference>  colRefs = new ArrayList<ColumnReference>();
+
+        for ( int i = 0; i < rcl.size(); i++ )
+        {
+            ResultColumn    rc = rcl.elementAt( i );
+            ColumnReference cr = rc.getReference();
+            if ( cr != null ) { colRefs.add( cr ); }
+        }
+
+        getColumnsFromList( map, colRefs );
+    }
+    
+    /** Add a list of columns to the the evolving map */
+    private void    getColumnsFromList
+        ( HashMap<String,ColumnReference> map, List<ColumnReference> colRefs )
+        throws StandardException
+    {
+        for ( ColumnReference cr : colRefs )
+        {
+            if ( cr.getTableName() == null )
+            {
+                ResultColumn    rc = _leftJoinFromList.bindColumnReference( cr );
+                TableName       tableName = new TableName( null, rc.getTableName(), getContextManager() );
+                cr = new ColumnReference( cr.getColumnName(), tableName, getContextManager() );
+            }
+
+            String  key = makeDCMKey( cr.getTableName(), cr.getColumnName() );
+            if ( map.get( key ) == null )
+            {
+                map.put( key, cr );
+            }
+        }
+    }
+
+    /** Make a HashMap key for a column in the driving column map of the LEFT JOIN */
+    private String  makeDCMKey( String tableName, String columnName )
+    {
+        return IdUtil.mkQualifiedName( tableName, columnName );
+    }
+
+    /** Boilerplate for binding an expression against a FromList */
+    void bindExpression( ValueNode value, FromList fromList )
+        throws StandardException
+    {
+        CompilerContext cc = getCompilerContext();
+        final int previousReliability = cc.getReliability();
+        
+        try {
+            cc.setReliability( previousReliability | CompilerContext.SQL_IN_ROUTINES_ILLEGAL );
+            
+            value.bindExpression
+                (
+                 fromList,
+                 new SubqueryList( getContextManager() ),
+                 new ArrayList<AggregateNode>()
+                 );
+        }
+        finally
+        {
+            // Restore previous compiler state
+            cc.setReliability( previousReliability );
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // optimize() BEHAVIOR
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+	public void optimizeStatement() throws StandardException
+	{
+		/* First optimize the left join */
+		_leftJoinCursor.optimizeStatement();
+
+		/* In language we always set it to row lock, it's up to store to
+		 * upgrade it to table lock.  This makes sense for the default read
+		 * committed isolation level and update lock.  For more detail, see
+		 * Beetle 4133.
+		 */
+		//lockMode = TransactionController.MODE_RECORD;
+
+        // now optimize the INSERT/UPDATE/DELETE actions
+        for ( MatchingClauseNode mcn : _matchingClauses )
+        {
+            mcn.optimize();
+        }
+	}
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // generate() BEHAVIOR
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    void generate( ActivationClassBuilder acb, MethodBuilder mb )
+							throws StandardException
+	{
+        int     clauseCount = _matchingClauses.size();
+
+		/* generate the parameters */
+		generateParameterValueSet(acb);
+
+        acb.pushGetResultSetFactoryExpression( mb );
+
+        // arg 1: the driving left join 
+        _leftJoinCursor.generate( acb, mb );
+
+        ConstantAction[]    clauseActions = new ConstantAction[ clauseCount ];
+        for ( int i = 0; i < clauseCount; i++ )
+        {
+            MatchingClauseNode  mcn = _matchingClauses.get( i );
+
+            mcn.generate( acb, i );
+            clauseActions[ i ] = mcn.makeConstantAction( acb );
+        }
+        _constantAction = getGenericConstantActionFactory().getMergeConstantAction( clauseActions );
+        
+        mb.callMethod
+            ( VMOpcode.INVOKEINTERFACE, (String) null, "getMergeResultSet", ClassName.ResultSet, 1 );
+	}
+    
+    @Override
+    public ConstantAction makeConstantAction() throws StandardException
+	{
+		return _constantAction;
+	}
+
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // Visitable BEHAVIOR
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+	/**
+	 * Accept the visitor for all visitable children of this node.
+	 * 
+	 * @param v the visitor
+	 *
+	 * @exception StandardException on error
+	 */
+    @Override
+	void acceptChildren(Visitor v)
+		throws StandardException
+	{
+        if ( _leftJoinCursor != null )
+        {
+            _leftJoinCursor.acceptChildren( v );
+        }
+        else
+        {
+            super.acceptChildren( v );
+
+            _targetTable.accept( v );
+            _sourceTable.accept( v );
+            _searchCondition.accept( v );
+        }
+        
+        for ( MatchingClauseNode mcn : _matchingClauses )
+        {
+            mcn.accept( v );
+        }
+	}
+
+    @Override
+    String statementToString()
+	{
+		return "MERGE";
+	}
 }

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/QueryTreeNode.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/QueryTreeNode.java?rev=1535360&r1=1535359&r2=1535360&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/QueryTreeNode.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/QueryTreeNode.java Thu Oct 24 12:41:59 2013
@@ -1392,6 +1392,13 @@ public abstract class QueryTreeNode impl
                 break;
             }
 		}
+        else if (
+                 (getCompilerContext().getReliability() & fragmentBitMask & CompilerContext.SQL_IN_ROUTINES_ILLEGAL)
+                 != 0
+                 )
+        {
+            sqlState = SQLState.LANG_ROUTINE_CANT_PERMIT_SQL;
+        }
 		else
 		{
             sqlState = SQLState.LANG_UNRELIABLE_QUERY_FRAGMENT;

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/UpdateNode.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/UpdateNode.java?rev=1535360&r1=1535359&r2=1535360&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/UpdateNode.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/UpdateNode.java Thu Oct 24 12:41:59 2013
@@ -78,8 +78,6 @@ public final class UpdateNode extends DM
 	protected FormatableBitSet 			readColsBitSet;
 	protected boolean 			positionedUpdate;
 
-    private     boolean         inMatchedClause;
-
 	/* Column name for the RowLocation in the ResultSet */
     static final String COLUMNNAME = "###RowLocationToUpdate";
 
@@ -88,17 +86,19 @@ public final class UpdateNode extends DM
 	 *
 	 * @param targetTableName	The name of the table to update
      * @param resultSet         The ResultSet that we will generate
-     * @param inMatchedClause   True if this UPDATE is part of a MATCHED ... THEN UPDATE clause of a MERGE statement.
+     * @param matchingClause   Non-null if this DML is part of a MATCHED clause of a MERGE statement.
      * @param cm                The context manager
 	 */
-    UpdateNode(TableName targetTableName,
-               ResultSetNode resultSet,
-               boolean  inMatchedClause,
-               ContextManager cm)
+    UpdateNode
+        (
+         TableName targetTableName,
+         ResultSetNode resultSet,
+         MatchingClauseNode matchingClause,
+         ContextManager cm
+         )
 	{
-        super(resultSet, cm);
+        super( resultSet, matchingClause, cm );
         this.targetTableName = targetTableName;
-        this.inMatchedClause = inMatchedClause;
 	}
 
 	/**
@@ -1341,7 +1341,7 @@ public final class UpdateNode extends DM
              * statement. If we clear the table name, then we will not be able to
              * resolve which table (target or source) holds the column.
 			 */
-			if ( !inMatchedClause ) { column.clearTableName(); }
+			if ( !inMatchingClause() ) { column.clearTableName(); }
 		}
 	}
 
@@ -1402,6 +1402,9 @@ public final class UpdateNode extends DM
         {
             ResultColumn rc = targetRCL.elementAt( i );
 
+            // defaults may already have been substituted for MERGE statements
+            if ( rc.wasDefaultColumn() ) { continue; }
+            
             if ( rc.hasGenerationClause() )
             {
                 ValueNode   resultExpression =

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/sqlgrammar.jj
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/sqlgrammar.jj?rev=1535360&r1=1535359&r2=1535360&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/sqlgrammar.jj (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/sqlgrammar.jj Thu Oct 24 12:41:59 2013
@@ -725,7 +725,7 @@ public class SQLParser
                                               getContextManager());
 
         StatementNode retval =
-            new DeleteNode(tableName, resultSet, getContextManager());
+            new DeleteNode(tableName, resultSet, null, getContextManager());
 
 		setUpAndLinkParameters();
 
@@ -758,7 +758,7 @@ public class SQLParser
                                               getContextManager());
 
         StatementNode retval =
-            new UpdateNode(tableName, resultSet, false, getContextManager());
+            new UpdateNode(tableName, resultSet, null, getContextManager());
 
 		setUpAndLinkParameters();
 
@@ -8510,6 +8510,7 @@ insertColumnsAndSource(QueryTreeNode tar
 							targetTable,
 							columnList,
 							queryExpression,
+							null,
 							targetProperties,
 							orderCols,
                             offsetClauses[ OFFSET_CLAUSE ],

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/BaseActivation.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/BaseActivation.java?rev=1535360&r1=1535359&r2=1535360&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/BaseActivation.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/BaseActivation.java Thu Oct 24 12:41:59 2013
@@ -28,6 +28,7 @@ import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.Hashtable;
+import java.util.Stack;
 import java.util.Vector;
 
 import	org.apache.derby.catalog.Dependable;
@@ -202,6 +203,11 @@ public abstract class BaseActivation imp
 	 */
 	private SQLSessionContext sqlSessionContextForChildren;
 
+    /**
+     * Stack of ConstantActions.
+     */
+    private Stack<ConstantAction>   constantActionStack = new Stack<ConstantAction>();
+
 	//Following is the position of the session table names list in savedObjects in compiler context
 	//This is updated to be the correct value at cursor generate time if the cursor references any session table names.
 	//If the cursor does not reference any session table names, this will stay negative
@@ -312,8 +318,23 @@ public abstract class BaseActivation imp
 		return preStmt;
 	}
 
-	public ConstantAction getConstantAction() {
-		return preStmt.getConstantAction();
+    public  ConstantAction    pushConstantAction( ConstantAction newConstantAction )
+    {
+        return constantActionStack.push( newConstantAction );
+    }
+
+    public  ConstantAction    popConstantAction()
+    {
+        return constantActionStack.pop();
+    }
+
+	public ConstantAction getConstantAction()
+    {
+        if ( constantActionStack.size() > 0 )
+        {
+            return constantActionStack.peek();
+        }
+        else { return preStmt.getConstantAction(); }
 	}
 
 

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/BulkTableScanResultSet.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/BulkTableScanResultSet.java?rev=1535360&r1=1535359&r2=1535360&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/BulkTableScanResultSet.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/BulkTableScanResultSet.java Thu Oct 24 12:41:59 2013
@@ -70,8 +70,11 @@ class BulkTableScanResultSet extends Tab
 	implements CursorResultSet
 {
 	private DataValueDescriptor[][] rowArray;
+    private RowLocation[]   rowLocations;
 	private int curRowPosition;
 	private int numRowsInArray;
+    private int         baseColumnCount;
+    private int         resultColumnCount;
 
 	private static int OUT_OF_ROWS = 0;
 
@@ -154,6 +157,41 @@ class BulkTableScanResultSet extends Tab
 					"rowsPerRead = " + rowsPerRead);
 			}
 		}
+
+        // determine whether we should fetch row locations
+        setRowLocationsState();
+
+        //
+        // The following code block was introduced to support the driving left join
+        // of the MERGE statement. If we are executing a MERGE statement, we need
+        // to fetch the row location of every row in the target table. If we are in this
+        // situation, then the last column in the candidate row will be a RowLocation template
+        // and the highest bit in accessedCols will be set. We want to smudge out this
+        // information before we ask the Store for rows. The Store will be confused if we ask
+        // for the RowLocation in the same row array as the actual base columns.
+        //
+        if ( fetchRowLocations )
+        {
+            resultColumnCount = accessedCols == null ? candidate.nColumns() : accessedCols.getNumBitsSet();
+            baseColumnCount = candidate.nColumns() - 1;
+            candidate.setRowArray( lopOffRowLocation() );
+
+            // remove the RowLocation from the accessed column map
+            if ( accessedCols == null )
+            {
+                accessedCols = new FormatableBitSet( baseColumnCount );
+                for ( int i = 0; i < baseColumnCount; i++ ) { accessedCols.set( i ); }
+            }
+            else
+            {
+                FormatableBitSet    newCols = new FormatableBitSet( baseColumnCount );
+                for ( int i = 0; i < baseColumnCount; i++ )
+                {
+                    if ( accessedCols.isSet( i ) ) { newCols.set( i ); }
+                }
+                accessedCols = newCols;
+            }
+        }
     }
 
     /**
@@ -253,6 +291,7 @@ class BulkTableScanResultSet extends Tab
 		*/
 		beginTime = getCurrentTimeMillis();
 		rowArray = new DataValueDescriptor[rowsPerRead][];
+        if ( fetchRowLocations ) { rowLocations = new RowLocation[ rowsPerRead ]; }
 
 		// we only allocate the first row -- the
 		// store clones as needed for the rest
@@ -264,6 +303,23 @@ class BulkTableScanResultSet extends Tab
 		openTime += getElapsedMillis(beginTime);
 	}
 
+    /**
+     * Get a blank row by cloning the candidate row and lopping off
+     * the trailing RowLocation column for scans done on
+     * behalf of MERGE statements.
+     */
+    private DataValueDescriptor[]   lopOffRowLocation()
+        throws StandardException
+    {
+        DataValueDescriptor[]   temp = candidate.getRowArrayClone();
+
+        int     count = temp.length - 1;
+        DataValueDescriptor[]   result = new DataValueDescriptor[ count ] ;
+        for ( int i = 0; i < count; i++ ) { result[ i ] = temp[ i ]; }
+
+        return result;
+    }
+
 	/**
 	 * Reopen the result set.  Delegate
 	 * most work to TableScanResultSet.reopenCore().
@@ -336,6 +392,17 @@ outer:		for (;;)
 					}
 
 					result = currentRow;
+                    if ( fetchRowLocations )
+                    {
+                        result = new ValueRow( resultColumnCount );
+                        int     idx = 1;
+
+                        for ( ; idx < resultColumnCount; idx++ )
+                        {
+                            result.setColumn( idx, currentRow.getColumn( idx ) );
+                        }
+                        result.setColumn( idx, rowLocations[ curRowPosition ] );
+                    }
 					break outer;
 				}
 			}
@@ -355,11 +422,11 @@ outer:		for (;;)
 		curRowPosition = -1;
 		numRowsInArray =
 				((GroupFetchScanController) scanController).fetchNextGroup(
-                                               rowArray, (RowLocation[]) null);
+                                               rowArray, rowLocations);
 
 		return numRowsInArray;
-
 	}
+
 	/**
 	 * If the result set has been opened,
 	 * close the open scan.  Delegate most
@@ -378,6 +445,7 @@ outer:		for (;;)
 		numRowsInArray = -1;
 		curRowPosition = -1;
 		rowArray = null;
+        rowLocations = null;
 	}
 
 	/**

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/DMLWriteResultSet.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/DMLWriteResultSet.java?rev=1535360&r1=1535359&r2=1535360&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/DMLWriteResultSet.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/DMLWriteResultSet.java Thu Oct 24 12:41:59 2013
@@ -103,7 +103,7 @@ abstract class DMLWriteResultSet extends
 		 * which case we do the objectifying in UpdateResultSet.  Beetle 4896.  Related bug entries:
 		 * 2432, 3383.
 		 */
-		needToObjectifyStream = (this.constantAction.getTriggerInfo() != null);
+        needToObjectifyStream = (this.constantAction.getTriggerInfo() != null);
 	}
 
     @Override

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/DeleteResultSet.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/DeleteResultSet.java?rev=1535360&r1=1535359&r2=1535360&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/DeleteResultSet.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/DeleteResultSet.java Thu Oct 24 12:41:59 2013
@@ -346,8 +346,8 @@ class DeleteResultSet extends DMLWriteRe
 
 		while ( row != null )
 		{
-			/* By convention, the last column for a delete contains a SQLRef
-			 * containing the RowLocation of the row to be deleted.  If we're
+			/* By convention, the last column for a delete contains a data value
+			 * wrapping the RowLocation of the row to be deleted.  If we're
 			 * doing a deferred delete, store the RowLocations in the
 			 * temporary conglomerate.  If we're not doing a deferred delete,
 			 * just delete the rows immediately.
@@ -406,7 +406,7 @@ class DeleteResultSet extends DMLWriteRe
 				baseRowLocation = 
 					(RowLocation) (rlColumn).getObject();
 
-				if (SanityManager.DEBUG)
+                if (SanityManager.DEBUG)
 				{
 					SanityManager.ASSERT(baseRowLocation != null,
 							"baseRowLocation is null");