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 [2/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/...

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/GenericConstantActionFactory.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/GenericConstantActionFactory.java?rev=1535360&r1=1535359&r2=1535360&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/GenericConstantActionFactory.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/GenericConstantActionFactory.java Thu Oct 24 12:41:59 2013
@@ -1113,4 +1113,33 @@ public class GenericConstantActionFactor
 	{
 		return new RevokeRoleConstantAction(roleNames, grantees);
 	}
+
+	/**
+	 * Make the ConstantAction for a WHEN [ NOT ] MATCHED clause.
+	 */
+	public	ConstantAction	getMatchingClauseConstantAction
+	(
+         int    clauseType,
+         String matchRefinementName,
+         int[]  thenColumns,
+         String resultSetFieldName,
+         String actionMethodName,
+         ConstantAction thenAction
+     )
+	{
+		return new MatchingClauseConstantAction
+            ( clauseType, matchRefinementName, thenColumns, resultSetFieldName, actionMethodName, thenAction );
+	}
+
+	/**
+	 * Make the ConstantAction for a MERGE statement.
+	 */
+	public	MergeConstantAction	getMergeConstantAction
+        (
+         ConstantAction[] matchingClauses
+         )
+	{
+		return new MergeConstantAction( matchingClauses );
+	}
+
 }

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/GenericResultSetFactory.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/GenericResultSetFactory.java?rev=1535360&r1=1535359&r2=1535360&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/GenericResultSetFactory.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/GenericResultSetFactory.java Thu Oct 24 12:41:59 2013
@@ -114,6 +114,17 @@ public class GenericResultSetFactory imp
 		return new DeleteResultSet(source, activation );
 	}
 
+	/**
+		@see ResultSetFactory#getMergeResultSet
+		@exception StandardException thrown on error
+	 */
+	public ResultSet getMergeResultSet(NoPutResultSet drivingLeftJoin)
+        throws StandardException
+    {
+		Activation activation = drivingLeftJoin.getActivation();
+		getAuthorizer( activation ).authorize( activation, Authorizer.SQL_WRITE_OP );
+		return new MergeResultSet( drivingLeftJoin, activation );
+    }
 
 	/**
 		@see ResultSetFactory#getDeleteCascadeResultSet

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/HashScanResultSet.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/HashScanResultSet.java?rev=1535360&r1=1535359&r2=1535360&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/HashScanResultSet.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/HashScanResultSet.java Thu Oct 24 12:41:59 2013
@@ -89,7 +89,6 @@ public class HashScanResultSet extends S
 	private boolean sameStartStopPosition;
 	private boolean skipNullKeyColumns;
 	private boolean keepAfterCommit;
-    private boolean includeRowLocations = false;
 
 	protected BackingStoreHashtable hashtable;
 	protected boolean eliminateDuplicates;		// set to true in DistinctScanResultSet
@@ -188,6 +187,9 @@ public class HashScanResultSet extends S
 		runTimeStatisticsOn = 
             getLanguageConnectionContext().getRunTimeStatisticsMode();
 
+        // determine whether we should fetch row locations
+        setRowLocationsState();
+        
 		compactRow =
 				getCompactRow(candidate, accessedCols, false);
 		recordConstructorTime();
@@ -275,7 +277,7 @@ public class HashScanResultSet extends S
                     runTimeStatisticsOn,
 					skipNullKeyColumns,
 					keepAfterCommit,
-					includeRowLocations);
+					fetchRowLocations);
 
 			if (runTimeStatisticsOn)
 			{

Added: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/MatchingClauseConstantAction.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/MatchingClauseConstantAction.java?rev=1535360&view=auto
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/MatchingClauseConstantAction.java (added)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/MatchingClauseConstantAction.java Thu Oct 24 12:41:59 2013
@@ -0,0 +1,314 @@
+/*
+
+   Derby - Class org.apache.derby.impl.sql.execute.MatchingClauseConstantAction
+
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to you under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+ */
+
+package org.apache.derby.impl.sql.execute;
+
+import java.io.ObjectOutput;
+import java.io.ObjectInput;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Properties;
+import org.apache.derby.iapi.error.StandardException;
+import org.apache.derby.iapi.reference.SQLState;
+import org.apache.derby.iapi.services.io.ArrayUtil;
+import org.apache.derby.iapi.services.io.Formatable;
+import org.apache.derby.iapi.services.io.StoredFormatIds;
+import org.apache.derby.iapi.services.loader.GeneratedMethod;
+import org.apache.derby.iapi.sql.ResultDescription;
+import org.apache.derby.iapi.sql.ResultSet;
+import org.apache.derby.iapi.sql.execute.ConstantAction;
+import org.apache.derby.iapi.sql.execute.CursorResultSet;
+import org.apache.derby.iapi.sql.execute.ExecRow;
+import org.apache.derby.iapi.sql.Activation;
+import org.apache.derby.iapi.types.SQLBoolean;
+import org.apache.derby.shared.common.sanity.SanityManager;
+
+/**
+ * Describes the execution machinery needed to evaluate a WHEN [ NOT ] MATCHING clause
+ * of a MERGE statement.
+ */
+public class MatchingClauseConstantAction implements ConstantAction, Formatable
+{
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // CONSTANTS
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Serial version produced by the serialver utility. Needed in order to
+     * make serialization work reliably across different compilers.
+     */
+    private static  final   long    serialVersionUID = -6725483265211088817L;
+
+    // for versioning during (de)serialization
+    private static final int FIRST_VERSION = 0;
+
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // STATE
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    // constructor args
+    private int _clauseType;
+    private String  _matchRefinementName;
+    private int[]   _thenColumnOffsets;
+    private String  _resultSetFieldName;
+    private String  _actionMethodName;
+    private ConstantAction  _thenAction;
+
+    // faulted in or built at run-time
+    private transient   GeneratedMethod _matchRefinementMethod;
+	private transient   TemporaryRowHolderImpl	_thenRows;
+    private transient   ResultSet           _actionRS;
+
+
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // CONSTRUCTOR
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Construct from thin air.
+     *
+     * @param   clauseType  WHEN_NOT_MATCHED_THEN_INSERT, WHEN_MATCHED_THEN_UPDATE, WHEN_MATCHED_THEN_DELETE
+     * @param   matchRefinementName Name of the method which evaluates the boolean expression in the WHEN clause.
+     * @param   thenColumns Column positions (1-based) from the driving left join which are needed to execute the THEN clause.
+     * @param   resultSetFieldName  Name of the field which will be stuffed at runtime with the temporary table of relevant rows.
+     * @param   actionMethodName    Name of the method which invokes the INSERT/UPDATE/DELETE action.
+     * @param   thenAction  The ConstantAction describing the associated INSERT/UPDATE/DELETE action.
+     */
+    public  MatchingClauseConstantAction
+        (
+         int    clauseType,
+         String matchRefinementName,
+         int[]  thenColumns,
+         String resultSetFieldName,
+         String actionMethodName,
+         ConstantAction thenAction
+         )
+    {
+        _clauseType = clauseType;
+        _matchRefinementName = matchRefinementName;
+        _thenColumnOffsets = ArrayUtil.copy( thenColumns );
+        _resultSetFieldName = resultSetFieldName;
+        _actionMethodName = actionMethodName;
+        _thenAction = thenAction;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // ACCESSORS
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    /** Get the clause type: WHEN_NOT_MATCHED_THEN_INSERT, WHEN_MATCHED_THEN_UPDATE, WHEN_MATCHED_THEN_DELETE */
+    public  int clauseType() { return _clauseType; }
+
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // ConstantAction BEHAVIOR
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+	public void	executeConstantAction( Activation activation )
+        throws StandardException
+    {
+        // nothing to do if no rows qualified
+        if ( _thenRows == null ) { return; }
+
+        CursorResultSet sourceRS = _thenRows.getResultSet();
+        GeneratedMethod actionGM = ((BaseActivation) activation).getMethod( _actionMethodName );
+
+        //
+        // Push the action-specific ConstantAction rather than the Merge statement's
+        // ConstantAction. The INSERT/UPDATE/DELETE expects the default ConstantAction
+        // to be appropriate to it.
+        //
+        try {
+            activation.pushConstantAction( _thenAction );
+
+            try {
+                //
+                // Poke the temporary table into the variable which will be pushed as
+                // an argument to the INSERT/UPDATE/DELETE action.
+                //
+                Field   resultSetField = activation.getClass().getField( _resultSetFieldName );
+                resultSetField.set( activation, sourceRS );
+
+                //
+                // Now execute the generated method which creates an InsertResultSet,
+                // UpdateResultSet, or DeleteResultSet.
+                //
+                Method  actionMethod = activation.getClass().getMethod( _actionMethodName );
+                _actionRS = (ResultSet) actionMethod.invoke( activation, null );
+            }
+            catch (Exception e) { throw StandardException.plainWrapException( e ); }
+
+            // this is where the INSERT/UPDATE/DELETE is processed
+            _actionRS.open();
+        }
+        finally
+        {
+            activation.popConstantAction();
+        }
+    }
+    
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // OTHER PUBLIC BEHAVIOR
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * <p>
+     * Run the matching refinement clause associated with this WHEN [ NOT ] MATCHED clause.
+     * The refinement is a boolean expression. Return the boolean value it resolves to.
+     * A boolean NULL is treated as false. If there is no refinement clause, then this method
+     * evaluates to true.
+     * </p>
+     */
+    boolean evaluateRefinementClause( Activation activation )
+        throws StandardException
+    {
+        if ( _matchRefinementName == null ) { return true; }
+        if ( _matchRefinementMethod == null )
+        {
+            _matchRefinementMethod = ((BaseActivation) activation).getMethod( _matchRefinementName );
+        }
+
+        SQLBoolean  result = (SQLBoolean) _matchRefinementMethod.invoke( activation );
+
+        if ( result.isNull() ) { return false; }
+        else { return result.getBoolean(); }
+    }
+
+    /**
+     * <p>
+     * Construct and buffer a row for the INSERT/UPDATE/DELETE
+     * action corresponding to this [ NOT ] MATCHED clause. The buffered row
+     * is built from columns in the passed-in row. The passed-in row is the SELECT list
+     * of the MERGE statement's driving left join.
+     * </p>
+     */
+    void    bufferThenRow
+        (
+         Activation activation,
+         ResultDescription selectDescription,
+         ExecRow selectRow
+         ) throws StandardException
+    {
+        int             thenRowLength = _thenColumnOffsets.length;
+        ValueRow    thenRow = new ValueRow( thenRowLength );
+        for ( int i = 0; i < thenRowLength; i++ )
+        {
+            thenRow.setColumn( i + 1, selectRow.getColumn( _thenColumnOffsets[ i ] ) );
+        }
+
+        if ( _thenRows == null ) { createThenRows( activation, selectDescription ); }
+        _thenRows.insert( thenRow );
+    }
+
+    /**
+     * <p>
+     * Release resources at the end.
+     * </p>
+     */
+    void    cleanUp()   throws StandardException
+    {
+        if ( _actionRS != null ) { _actionRS.close(); }
+        if ( _thenRows != null ) { _thenRows.close(); }
+    }
+
+    /**
+     * <p>
+     * Create the temporary table for holding the rows which are buffered up
+     * for bulk-processing after the driving left join completes.
+     * </p>
+     */
+    private void    createThenRows( Activation activation, ResultDescription selectDescription )
+        throws StandardException
+    {
+        ResultDescription   thenDescription = activation.getLanguageConnectionContext().getLanguageFactory().getResultDescription
+            ( selectDescription, _thenColumnOffsets );
+
+        _thenRows = new TemporaryRowHolderImpl( activation, new Properties(), thenDescription );
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // Formatable BEHAVIOR
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+	/**
+	 * Read this object from a stream of stored objects.
+	 *
+	 * @param in read this.
+	 *
+	 * @exception IOException					thrown on error
+	 * @exception ClassNotFoundException		thrown on error
+	 */
+	public void readExternal( ObjectInput in )
+		 throws IOException, ClassNotFoundException
+	{
+        // as the persistent form evolves, switch on this value
+        int oldVersion = in.readInt();
+
+        _clauseType = in.readInt();
+        _matchRefinementName = in.readUTF();
+        _thenColumnOffsets = ArrayUtil.readIntArray( in );
+        _resultSetFieldName = in.readUTF();
+        _actionMethodName = in.readUTF();
+        _thenAction = (ConstantAction) in.readObject();
+	}
+
+	/**
+	 * Write this object to a stream of stored objects.
+	 *
+	 * @param out write bytes here.
+	 *
+	 * @exception IOException		thrown on error
+	 */
+	public void writeExternal( ObjectOutput out )
+		 throws IOException
+	{
+		out.writeInt( FIRST_VERSION );
+
+        out.writeInt( _clauseType );
+        out.writeUTF( _matchRefinementName );
+        ArrayUtil.writeIntArray( out, _thenColumnOffsets );
+        out.writeUTF( _resultSetFieldName );
+        out.writeUTF( _actionMethodName );
+        out.writeObject( _thenAction );
+	}
+ 
+	/**
+	 * Get the formatID which corresponds to this class.
+	 *
+	 *	@return	the formatID of this class
+	 */
+	public	int	getTypeFormatId()	{ return StoredFormatIds.MATCHING_CLAUSE_CONSTANT_ACTION_V01_ID; }
+
+}

Propchange: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/MatchingClauseConstantAction.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/MergeConstantAction.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/MergeConstantAction.java?rev=1535360&view=auto
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/MergeConstantAction.java (added)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/MergeConstantAction.java Thu Oct 24 12:41:59 2013
@@ -0,0 +1,156 @@
+/*
+
+   Derby - Class org.apache.derby.impl.sql.execute.MergeConstantAction
+
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to you under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+ */
+
+package org.apache.derby.impl.sql.execute;
+
+import java.io.ObjectOutput;
+import java.io.ObjectInput;
+import java.io.IOException;
+import org.apache.derby.iapi.error.StandardException;
+import org.apache.derby.iapi.services.io.ArrayUtil;
+import org.apache.derby.iapi.services.io.Formatable;
+import org.apache.derby.iapi.services.io.StoredFormatIds;
+import org.apache.derby.iapi.sql.Activation;
+import org.apache.derby.iapi.sql.execute.ConstantAction;
+import org.apache.derby.shared.common.sanity.SanityManager;
+
+/**
+ * Describes the execution machinery needed to evaluate a MERGE statement.
+ */
+public class MergeConstantAction implements ConstantAction, Formatable
+{
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // CONSTANTS
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Serial version produced by the serialver utility. Needed in order to
+     * make serialization work reliably across different compilers.
+     */
+    //private static  final   long    serialVersionUID = -6725483265211088817L;
+
+    // for versioning during (de)serialization
+    private static final int FIRST_VERSION = 0;
+
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // STATE
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    // constructor args
+    private MatchingClauseConstantAction[]  _matchingClauses;
+
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // CONSTRUCTOR
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Construct from thin air.
+     *
+     * @param   matchingClauses Constant actions for WHEN [ NOT ] MATCHED clauses.
+     */
+    public  MergeConstantAction
+        (
+         ConstantAction[] matchingClauses
+         )
+    {
+        int     clauseCount = matchingClauses.length;
+        _matchingClauses = new MatchingClauseConstantAction[ clauseCount ];
+        for ( int i = 0; i < clauseCount; i++ )
+        { _matchingClauses[ i ] = (MatchingClauseConstantAction) matchingClauses[ i ]; }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // ACCESSORS
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    /** Get the number of matching clauses */
+    public  int matchingClauseCount() { return _matchingClauses.length; }
+
+    /** Get the ith (0-based) matching clause */
+    public  MatchingClauseConstantAction  getMatchingClause( int idx )  { return _matchingClauses[ idx ]; }
+
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // ConstantAction BEHAVIOR
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+	public void	executeConstantAction( Activation activation )
+        throws StandardException
+    {}
+    
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // Formatable BEHAVIOR
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+	/**
+	 * Read this object from a stream of stored objects.
+	 *
+	 * @param in read this.
+	 *
+	 * @exception IOException					thrown on error
+	 * @exception ClassNotFoundException		thrown on error
+	 */
+	public void readExternal( ObjectInput in )
+		 throws IOException, ClassNotFoundException
+	{
+        // as the persistent form evolves, switch on this value
+        int oldVersion = in.readInt();
+
+        _matchingClauses = new MatchingClauseConstantAction[ in.readInt() ];
+        for ( int i = 0; i < _matchingClauses.length; i++ )
+        { _matchingClauses[ i ] = (MatchingClauseConstantAction) in.readObject(); }
+	}
+
+	/**
+	 * Write this object to a stream of stored objects.
+	 *
+	 * @param out write bytes here.
+	 *
+	 * @exception IOException		thrown on error
+	 */
+	public void writeExternal( ObjectOutput out )
+		 throws IOException
+	{
+		out.writeInt( FIRST_VERSION );
+
+        out.writeInt( _matchingClauses.length );
+        for ( int i = 0; i < _matchingClauses.length; i++ ) { out.writeObject( _matchingClauses[ i ] ); }
+	}
+ 
+	/**
+	 * Get the formatID which corresponds to this class.
+	 *
+	 *	@return	the formatID of this class
+	 */
+	public	int	getTypeFormatId()	{ return StoredFormatIds.MERGE_CONSTANT_ACTION_V01_ID; }
+
+}

Propchange: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/MergeConstantAction.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/MergeResultSet.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/MergeResultSet.java?rev=1535360&view=auto
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/MergeResultSet.java (added)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/MergeResultSet.java Thu Oct 24 12:41:59 2013
@@ -0,0 +1,220 @@
+/*
+
+   Derby - Class org.apache.derby.impl.sql.execute.MergeResultSet
+
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to you under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+ */
+
+package org.apache.derby.impl.sql.execute;
+
+import org.apache.derby.iapi.error.StandardException;
+import org.apache.derby.iapi.reference.SQLState;
+import org.apache.derby.iapi.services.context.ContextManager;
+import org.apache.derby.iapi.services.loader.GeneratedMethod;
+import org.apache.derby.shared.common.sanity.SanityManager;
+import org.apache.derby.iapi.sql.Activation;
+import org.apache.derby.iapi.sql.execute.ConstantAction;
+import org.apache.derby.iapi.sql.execute.ExecIndexRow;
+import org.apache.derby.iapi.sql.execute.ExecRow;
+import org.apache.derby.iapi.sql.execute.NoPutResultSet;
+import org.apache.derby.iapi.types.DataValueDescriptor;
+import org.apache.derby.iapi.types.RowLocation;
+
+/**
+ * INSERT/UPDATE/DELETE a target table based on how it outer joins
+ * with a driving table. For a description of how Derby processes
+ * the MERGE statement, see the header comment on MergeNode.
+ */
+class MergeResultSet extends NoRowsResultSetImpl 
+{
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // CONSTANTS
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // STATE
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    private NoPutResultSet          _drivingLeftJoin;
+    private MergeConstantAction _constants;
+
+    private ExecRow                 _row;
+    private long                        _rowCount;
+
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // CONSTRUCTOR
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Construct from a driving left join and an Activation.
+     */
+    MergeResultSet
+        (
+         NoPutResultSet drivingLeftJoin, 
+         Activation activation
+         )
+        throws StandardException
+    {
+        super( activation );
+        _drivingLeftJoin = drivingLeftJoin;
+        _constants = (MergeConstantAction) activation.getConstantAction();
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // BEHAVIOR
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    public final long   modifiedRowCount() { return _rowCount + RowUtil.getRowCountBase(); }
+
+    public void open() throws StandardException
+    {
+        setup();
+
+        boolean rowsFound = collectAffectedRows();
+        if ( !rowsFound )
+        {
+            activation.addWarning( StandardException.newWarning( SQLState.LANG_NO_ROW_FOUND ) );
+        }
+
+        // now execute the INSERT/UPDATE/DELETE actions
+        int         clauseCount = _constants.matchingClauseCount();
+        for ( int i = 0; i < clauseCount; i++ )
+        {
+            _constants.getMatchingClause( i ).executeConstantAction( activation );
+        }
+
+        cleanUp();
+        endTime = getCurrentTimeMillis();
+    }
+
+    @Override
+    void  setup() throws StandardException
+    {
+        super.setup();
+
+        _rowCount = 0L;
+        _drivingLeftJoin.openCore();
+    }
+    
+    /**
+     * Clean up resources and call close on data members.
+     */
+    public void close() throws StandardException
+    {
+        super.close();
+    }
+
+    public void cleanUp() throws StandardException
+    {
+        int         clauseCount = _constants.matchingClauseCount();
+        for ( int i = 0; i < clauseCount; i++ )
+        {
+            _constants.getMatchingClause( i ).cleanUp();
+        }
+    }
+
+
+    public void finish() throws StandardException
+    {
+        _drivingLeftJoin.finish();
+        super.finish();
+    }
+
+    /**
+     * <p>
+     * Loop through the rows in the driving left join.
+     * </p>
+     */
+    boolean  collectAffectedRows() throws StandardException
+    {
+        DataValueDescriptor     rlColumn;
+        RowLocation             baseRowLocation;
+        boolean rowsFound = false;
+
+        while ( true )
+        {
+            // may need to objectify stream columns here.
+            // see DMLWriteResultSet.getNextRowCoure(NoPutResultSet)
+            _row =  _drivingLeftJoin.getNextRowCore();
+            if ( _row == null ) { break; }
+
+            // By convention, the last column for the driving left join contains a data value
+            // containing the RowLocation of the target row.
+
+            rowsFound = true;
+
+            rlColumn = _row.getColumn( _row.nColumns() );
+            baseRowLocation = null;
+
+            boolean matched = false;
+            if ( rlColumn != null )
+            {
+                if ( !rlColumn.isNull() )
+                {
+                    matched = true;
+                    baseRowLocation = (RowLocation) rlColumn.getObject();
+                }
+            }
+
+            // find the first clause which applies to this row
+            MatchingClauseConstantAction    matchingClause = null;
+            int         clauseCount = _constants.matchingClauseCount();
+            for ( int i = 0; i < clauseCount; i++ )
+            {
+                MatchingClauseConstantAction    candidate = _constants.getMatchingClause( i );
+                boolean isWhenMatchedClause = false;
+                
+                switch ( candidate.clauseType() )
+                {
+                case ConstantAction.WHEN_MATCHED_THEN_UPDATE:
+                case ConstantAction.WHEN_MATCHED_THEN_DELETE:
+                    isWhenMatchedClause = true;
+                    break;
+                }
+
+                boolean considerClause = (matched == isWhenMatchedClause);
+
+                if ( considerClause )
+                {
+                    if ( candidate.evaluateRefinementClause( activation ) )
+                    {
+                        matchingClause = candidate;
+                        break;
+                    }
+                }
+            }
+
+            if ( matchingClause != null )
+            {
+                matchingClause.bufferThenRow( activation, _drivingLeftJoin.getResultDescription(), _row );
+                _rowCount++;
+            }
+        }
+
+        return rowsFound;
+    }
+
+}

Propchange: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/MergeResultSet.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/ScanResultSet.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/ScanResultSet.java?rev=1535360&r1=1535359&r2=1535360&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/ScanResultSet.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/ScanResultSet.java Thu Oct 24 12:41:59 2013
@@ -31,6 +31,7 @@ import org.apache.derby.iapi.sql.execute
 import org.apache.derby.iapi.sql.execute.ExecutionContext;
 import org.apache.derby.iapi.store.access.TransactionController;
 import org.apache.derby.iapi.transaction.TransactionControl;
+import org.apache.derby.iapi.types.RowLocation;
 
 /**
  * Abstract <code>ResultSet</code> class for <code>NoPutResultSet</code>s which
@@ -88,7 +89,10 @@ abstract class ScanResultSet extends NoP
      * need to be pulled from the underlying object to be scanned.
      * Set from the PreparedStatement's saved objects, if it exists.
      */
-    protected final FormatableBitSet accessedCols;
+    protected FormatableBitSet accessedCols;
+
+    /** true if the scan should pick up row locations */
+    protected boolean fetchRowLocations = false;
 
 	public String tableName;
 	public String indexName;
@@ -189,6 +193,18 @@ abstract class ScanResultSet extends NoP
         }
     }
 
+    /** Determine whether this scan should return row locations */
+    protected   void    setRowLocationsState()
+        throws StandardException
+    {
+        fetchRowLocations =
+            (
+             (indexName == null) &&
+             (candidate.nColumns() > 0) &&
+             ( candidate.getColumn( candidate.nColumns() ) instanceof RowLocation )
+             );
+    }
+
     /**
      * Translate isolation level from language to store.
      *

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/store/access/heap/HeapRowLocation.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/store/access/heap/HeapRowLocation.java?rev=1535360&r1=1535359&r2=1535360&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/store/access/heap/HeapRowLocation.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/store/access/heap/HeapRowLocation.java Thu Oct 24 12:41:59 2013
@@ -98,7 +98,7 @@ public class HeapRowLocation extends Dat
 	}
 
 	public Object getObject() {
-		return null;
+		return this;
 	}
 
 	public DataValueDescriptor cloneValue(boolean forceMaterialization) {

Modified: db/derby/code/trunk/java/engine/org/apache/derby/loc/messages.xml
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/loc/messages.xml?rev=1535360&r1=1535359&r2=1535360&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/loc/messages.xml (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/loc/messages.xml Thu Oct 24 12:41:59 2013
@@ -2166,7 +2166,7 @@ Guide.
 
             <msg>
                 <name>42XA5</name>
-                <text>Routine '{0}' may issue SQL and therefore cannot appear in a Generation Clause.</text>
+                <text>Routine '{0}' may issue SQL and therefore cannot appear in this context.</text>
                 <arg>routineName</arg>
             </msg>
 

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/GeneratedColumnsHelper.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/GeneratedColumnsHelper.java?rev=1535360&r1=1535359&r2=1535360&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/GeneratedColumnsHelper.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/GeneratedColumnsHelper.java Thu Oct 24 12:41:59 2013
@@ -143,6 +143,21 @@ public class GeneratedColumnsHelper exte
         ps.close();
     }
     
+    /**
+     * Run a good update statement with an expected row count.
+     * @throws SQLException 
+     */
+    protected void    goodUpdate( Connection conn, String update, int expectedRowCount ) throws SQLException
+    {
+        PreparedStatement    ps = chattyPrepare( conn, update );
+
+        int actualRowCount = ps.executeUpdate();
+        ps.close();
+
+        println( "Expecting to touch " + expectedRowCount + " rows." );
+        assertEquals( expectedRowCount, actualRowCount );
+    }
+    
 	protected	static	ResultSet	executeQuery( Statement stmt, String text )
 		throws SQLException
 	{
@@ -263,6 +278,15 @@ public class GeneratedColumnsHelper exte
     }
 
     /**
+     * Assert that the statement text, when executed, raises no warnings.
+     */
+    protected void    expectNoWarning( Connection conn, String query )
+        throws Exception
+    {
+        expectExecutionWarnings( conn, new String[] { }, query );
+    }
+
+    /**
      * Assert that the statement text, when executed, raises a warning.
      */
     protected void    expectExecutionWarnings( Connection conn, String[] sqlStates, String query )

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/MergeStatementTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/MergeStatementTest.java?rev=1535360&r1=1535359&r2=1535360&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/MergeStatementTest.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/MergeStatementTest.java Thu Oct 24 12:41:59 2013
@@ -68,6 +68,10 @@ public class MergeStatementTest extends 
     private static  final   String      COLUMN_NOT_IN_TABLE = "42X14";
     private static  final   String      COLUMN_COUNT_MISMATCH = "42802";
     private static  final   String      DUPLICATE_SET_COLUMNS = "42X16";
+    private static  final   String      MISSING_TABLE = "42X05";
+    private static  final   String      NO_ROWS_AFFECTED = "02000";
+
+    private static  final   String[]    TRIGGER_HISTORY_COLUMNS = new String[] { "ACTION", "ACTION_VALUE" };
 
     ///////////////////////////////////////////////////////////////////////////////////
     //
@@ -75,6 +79,9 @@ public class MergeStatementTest extends 
     //
     ///////////////////////////////////////////////////////////////////////////////////
 
+    // In-memory table for recording trigger actions. Each row is a { actionName, actionValue } pair.
+    private static  ArrayList<String[]> _triggerHistory = new ArrayList<String[]>();
+
     ///////////////////////////////////////////////////////////////////////////////////
     //
     // CONSTRUCTOR
@@ -265,7 +272,7 @@ public class MergeStatementTest extends 
               "merge into t1\n" +
               "using t2\n" +
               "on t1.c1 = t2.c1\n" +
-              "when matched and t3.c2 = t2.c2 then update set c3 = t2.c3\n"
+              "when matched and t3.c2 = t2.c2 then update set c1_4 = t2.c3\n"
               );
         expectCompilationError
             ( dboConnection, COLUMN_OUT_OF_SCOPE,
@@ -287,15 +294,35 @@ public class MergeStatementTest extends 
               "when not matched and t1.c2 is null then insert ( c2 ) values ( t2.c2 )\n"
               );
 
-        // Should fail at run time because the function modifies sql data.
-        // But for now, this doesn't make it past the bind() phase.
+        // Boolean expressions can't contain functions which issue SQL
         expectCompilationError
-            ( dboConnection, NOT_IMPLEMENTED,
+            ( dboConnection, ROUTINE_CANT_ISSUE_SQL,
               "merge into t1\n" +
               "using t2\n" +
               "on t1.c1 = t2.c1 and t2.c2 = illegalFunction()\n" +
               "when matched then delete\n"
               );
+        expectCompilationError
+            ( dboConnection, ROUTINE_CANT_ISSUE_SQL,
+              "merge into t1\n" +
+              "using t2\n" +
+              "on t1.c1 = t2.c1\n" +
+              "when matched and t2.c2 = illegalFunction() then delete\n"
+              );
+        expectCompilationError
+            ( dboConnection, ROUTINE_CANT_ISSUE_SQL,
+              "merge into t1\n" +
+              "using t2\n" +
+              "on t1.c1 = t2.c1\n" +
+              "when matched and t2.c2 = illegalFunction() then update set c2 = t2.c2\n"
+              );
+        expectCompilationError
+            ( dboConnection, ROUTINE_CANT_ISSUE_SQL,
+              "merge into t1\n" +
+              "using t2\n" +
+              "on t1.c1 = t2.c1\n" +
+              "when not matched and t2.c2 = illegalFunction() then insert ( c2 ) values ( t2.c2 )\n"
+              );
 
         // Can only specify DEFAULT as the value of a generated column.
         expectCompilationError
@@ -389,8 +416,11 @@ public class MergeStatementTest extends 
               );
 
         // Trigger tansition tables may not be used as target tables.
+        // XXX ??? FIXME This needs to be revisited. We're getting an Assert here
+        // because the code thinks that the target table is a FromVTI
+        /**
         expectCompilationError
-            ( dboConnection, TARGET_MUST_BE_BASE,
+            ( dboConnection, MISSING_TABLE,
               "create trigger trig1 after update on t1\n" +
               "referencing old table as old_cor new table as new_cor\n" +
               "for each statement\n" +
@@ -400,7 +430,7 @@ public class MergeStatementTest extends 
               "when not matched then insert ( c2 ) values ( t2.c2 )\n"
               );
         expectCompilationError
-            ( dboConnection, TARGET_MUST_BE_BASE,
+            ( dboConnection, MISSING_TABLE,
               "create trigger trig2 after update on t1\n" +
               "referencing old table as old_cor new table as new_cor\n" +
               "for each statement\n" +
@@ -409,6 +439,7 @@ public class MergeStatementTest extends 
               "on old_cor.c1 = t2.c1\n" +
               "when not matched then insert ( c2 ) values ( t2.c2 )\n"
               );
+        */
 
         // Columns may not be SET twice in a MATCHED ... THEN UPDATE clause
         expectCompilationError
@@ -438,37 +469,43 @@ public class MergeStatementTest extends 
               );
 
         //
-        // The following syntax is actually good, but the compiler rejects these
-        // statements because we haven't finished implementing MERGE.
+        // The following syntax is actually good and the statements affect no rows.
         //
-        expectCompilationError
-            ( dboConnection, NOT_IMPLEMENTED,
+        expectExecutionWarning
+            ( dboConnection, NO_ROWS_AFFECTED,
               "merge into t1\n" +
-              "using table( integerList() ) i\n" +
-              "on t1.c1 = i.s_r\n" +
+              "using t2\n" +
+              "on t1.c1 = t2.c1\n" +
               "when matched then delete\n"
               );
-        expectCompilationError
-            ( dboConnection, NOT_IMPLEMENTED,
+        expectExecutionWarning
+            ( dboConnection, NO_ROWS_AFFECTED,
               "merge into t1\n" +
               "using v2\n" +
               "on t1.c1 = v2.c1\n" +
               "when matched then delete\n"
               );
-        expectCompilationError
-            ( dboConnection, NOT_IMPLEMENTED,
+        expectExecutionWarning
+            ( dboConnection, NO_ROWS_AFFECTED,
               "merge into t1\n" +
               "using t2\n" +
               "on t1.c1 = t2.c1\n" +
-              "when matched then delete\n"
+              "when matched and t1.c2 = t2.c2 then delete\n"
               );
-        expectCompilationError
-            ( dboConnection, NOT_IMPLEMENTED,
+
+        // good statement. no rows affected but no warning because sourceTable is not empty
+        expectNoWarning
+            ( dboConnection,
               "merge into t1\n" +
-              "using t2\n" +
-              "on t1.c1 = t2.c1\n" +
-              "when matched and t1.c2 = t2.c2 then delete\n"
+              "using table( integerList() ) i\n" +
+              "on t1.c1 = i.s_r\n" +
+              "when matched then delete\n"
               );
+        
+        //
+        // The following syntax is actually good, but the compiler rejects these
+        // statements because we haven't finished implementing MERGE.
+        //
         expectCompilationError
             ( dboConnection, NOT_IMPLEMENTED,
               "merge into t1\n" +
@@ -511,8 +548,17 @@ public class MergeStatementTest extends 
               "on t1.c1 = t2.c1\n" +
               "when not matched then insert ( c2, c3 ) values ( t2.c2, default )\n"
               );
+        expectCompilationError
+            ( dboConnection, NOT_IMPLEMENTED,
+              "merge into t1\n" +
+              "using t2\n" +
+              "on t1.c1 = t2.c1\n" +
+              "when matched and t1.c2 != t2.c2 then update set c2 = t2.c2\n" +
+              "when not matched then insert ( c2 ) values ( t2.c2 )\n"
+              );
 
         // Using a trigger transition table as a source table is probably ok.
+        /*
         expectCompilationError
             ( dboConnection, NOT_IMPLEMENTED,
               "create trigger trig3 after update on t2\n" +
@@ -533,6 +579,7 @@ public class MergeStatementTest extends 
               "on t1.c1 = old_cor.c1\n" +
               "when not matched then insert ( c2 ) values ( old_cor.c2 )\n"
               );
+        */
 
         // it's probably ok to specify default values for generated columns in MATCHED ... THEN UPDATE
         expectCompilationError
@@ -553,6 +600,569 @@ public class MergeStatementTest extends 
         goodStatement( dboConnection, "drop table t1" );
     }
     
+    /**
+     * <p>
+     * Test the delete action.
+     * </p>
+     */
+    public  void    test_002_deleteAction()
+        throws Exception
+    {
+        Connection  dboConnection = openUserConnection( TEST_DBO );
+
+        goodStatement
+            ( dboConnection,
+              "create table t1_002( c1 int, c2 int, c3 int generated always as ( c1 + c2 ), c1_4 int )" );
+        goodStatement
+            ( dboConnection,
+              "create table t2_002( c1 int, c2 int, c3 int, c4 int, c5 varchar( 5 ) )" );
+
+        // a DELETE action without a matching refinement clause
+        vet_002
+            (
+             dboConnection,
+             "merge into t1_002\n" +
+             "using t2_002\n" +
+             "on 2 * t1_002.c2 = 2 * t2_002.c2\n" +
+             "when matched then delete\n",
+             4,
+             new String[][]
+             {
+                 { "5", "5", "10", "5" },   
+                 { "6", "20", "26", "40" },    
+             }
+             );
+        
+        // a DELETE action with a matching refinement clause
+        vet_002
+            (
+             dboConnection,
+             "merge into t1_002\n" +
+             "using t2_002\n" +
+             "on 2 * t1_002.c2 = 2 * t2_002.c2\n" +
+             "when matched and c1_4 = 5 then delete\n",
+             3,
+             new String[][]
+             {
+                 { "1", "2", "3", "4" },  
+                 { "5", "5", "10", "5" },   
+                 { "6", "20", "26", "40" },    
+             }
+             );
+
+        goodStatement( dboConnection, "drop table t2_002" );
+        goodStatement( dboConnection, "drop table t1_002" );
+    }
+    private void    vet_002
+        (
+         Connection conn,
+         String query,
+         int    rowsAffected,
+         String[][] expectedResults
+         )
+        throws Exception
+    {
+        vet_002( conn, query, rowsAffected, expectedResults, false );
+        vet_002( conn, query, rowsAffected, expectedResults, true );
+    }
+    private void    vet_002
+        (
+         Connection conn,
+         String query,
+         int    rowsAffected,
+         String[][] expectedResults,
+         boolean    useHashJoinStrategy
+         )
+        throws Exception
+    {
+        if ( useHashJoinStrategy ) { query = makeHashJoinMerge( query ); }
+
+        populate_002( conn );
+        goodUpdate( conn, query, rowsAffected );
+        assertResults( conn, "select * from t1_002 order by c1", expectedResults, false );
+    }
+    private void    populate_002( Connection conn )
+        throws Exception
+    {
+        goodStatement( conn, "delete from t2_002" );
+        goodStatement( conn, "delete from t1_002" );
+
+        goodStatement
+            ( conn,
+              "insert into t1_002( c1, c2, c1_4 ) values ( 1, 2, 4 ), (2, 2, 5), (3, 3, 5), (4, 4, 5), (5, 5, 5), ( 6, 20, 40 )"
+              );
+        goodStatement
+            ( conn,
+              "insert into t2_002( c1, c2, c3, c4, c5 ) values ( 1, 2, 3, 4, 'five' ), ( 2, 3, 3, 4, 'five' ), ( 3, 4, 3, 4, 'five' ), ( 4, 200, 300, 400, 'five' )"
+              );
+    }
+    
+    /**
+     * <p>
+     * Test delete action involving subsequent cascaded deletes.
+     * </p>
+     */
+    public  void    test_003_cascadingDeleteAction()
+        throws Exception
+    {
+        Connection  dboConnection = openUserConnection( TEST_DBO );
+
+        goodStatement
+            ( dboConnection,
+              "create table t1_003( c1 int, c2 int primary key, c3 int )" );
+        goodStatement
+            ( dboConnection,
+              "create table t2_003( c4 int, c5 int, c6 int )" );
+        goodStatement
+            ( dboConnection,
+              "create table t3_003( c1 int, c2 int references t1_003( c2 ) on delete cascade, c3 int )" );
+
+        // a cascading DELETE action without a matching refinement clause
+        vet_003
+            (
+             dboConnection,
+             "merge into t1_003\n" +
+             "using t2_003\n" +
+             "on 2 * t1_003.c1 = 2 * t2_003.c4\n" +
+             "when matched then delete\n",
+             2,
+             new String[][]
+             {
+                 { "-3", "30", "200" },  
+                 { "5", "50", "500" },    
+                 { "6", "60", "600" },    
+             },
+             new String[][]
+             {
+                 { "-3", "30", "300" },  
+                 { "5", "50", "500" },    
+                 { "6", "60", "600" },    
+             }
+             );
+
+        // a cascading DELETE action with a matching refinement clause
+        vet_003
+            (
+             dboConnection,
+             "merge into t1_003\n" +
+             "using t2_003\n" +
+             "on 2 * t1_003.c1 = 2 * t2_003.c4\n" +
+             "when matched and c3 = 200 then delete\n",
+             1,
+             new String[][]
+             {
+                 { "-3", "30", "200" },  
+                 { "1", "10", "100" },   
+                 { "5", "50", "500" },    
+                 { "6", "60", "600" },    
+             },
+             new String[][]
+             {
+                 { "-3", "30", "300" },  
+                 { "1", "10", "100" },   
+                 { "5", "50", "500" },    
+                 { "6", "60", "600" },    
+             }
+             );
+
+        goodStatement( dboConnection, "drop table t3_003" );
+        goodStatement( dboConnection, "drop table t2_003" );
+        goodStatement( dboConnection, "drop table t1_003" );
+    }
+    private void    vet_003
+        (
+         Connection conn,
+         String query,
+         int    rowsAffected,
+         String[][] expectedT1Results,
+         String[][] expectedT3Results
+         )
+        throws Exception
+    {
+        vet_003( conn, query, rowsAffected, expectedT1Results, expectedT3Results, false );
+        vet_003( conn, query, rowsAffected, expectedT1Results, expectedT3Results, true );
+    }
+    private void    vet_003
+        (
+         Connection conn,
+         String query,
+         int    rowsAffected,
+         String[][] expectedT1Results,
+         String[][] expectedT3Results,
+         boolean    useHashJoinStrategy
+         )
+        throws Exception
+    {
+        if ( useHashJoinStrategy ) { query = makeHashJoinMerge( query ); }
+
+        populate_003( conn );
+        goodUpdate( conn, query, rowsAffected );
+        assertResults( conn, "select * from t1_003 order by c1", expectedT1Results, false );
+        assertResults( conn, "select * from t3_003 order by c1", expectedT3Results, false );
+    }
+    private void    populate_003( Connection conn )
+        throws Exception
+    {
+        goodStatement( conn, "delete from t3_003" );
+        goodStatement( conn, "delete from t2_003" );
+        goodStatement( conn, "delete from t1_003" );
+
+        goodStatement
+            ( conn,
+              "insert into t1_003( c1, c2, c3 ) values ( 1, 10, 100 ), (2, 20, 200), ( -3, 30, 200 ), ( 5, 50, 500 ), ( 6, 60, 600 )"
+              );
+        goodStatement
+            ( conn,
+              "insert into t2_003( c4, c5, c6 ) values ( 1, 10, 100 ), (2, 20, 200), ( 3, 30, 300 ), ( 4, 40, 400 )"
+              );
+        goodStatement
+            ( conn,
+              "insert into t3_003( c1, c2, c3 ) values ( 1, 10, 100 ), (2, 20, 200), ( -3, 30, 300 ), ( 5, 50, 500 ), ( 6, 60, 600 )"
+              );
+    }
+    
+    /**
+     * <p>
+     * Test delete action involving before and after statement triggers.
+     * </p>
+     */
+    public  void    test_004_deleteActionStatementTriggers()
+        throws Exception
+    {
+        Connection  dboConnection = openUserConnection( TEST_DBO );
+
+        //
+        // create schema
+        //
+        goodStatement
+            ( dboConnection,
+              "create table t1_004( c1 int, c2 int, c3 int )" );
+        goodStatement
+            ( dboConnection,
+              "create table t2_004( c1 int generated always as identity, c2 int )" );
+        goodStatement
+            ( dboConnection,
+              "create procedure countRows_004\n" +
+              "(\n" +
+              "    candidateName varchar( 20 ),\n" +
+              "    actionString varchar( 20 )\n" +
+              ")\n" +
+              "language java parameter style java reads sql data\n" +
+              "external name 'org.apache.derbyTesting.functionTests.tests.lang.MergeStatementTest.countRows'\n"
+              );
+        goodStatement
+            ( dboConnection,
+              "create procedure truncateTriggerHistory_004()\n" +
+              "language java parameter style java no sql\n" +
+              "external name 'org.apache.derbyTesting.functionTests.tests.lang.MergeStatementTest.truncateTriggerHistory'\n"
+              );
+        goodStatement
+            ( dboConnection,
+              "create function history_004()\n" +
+              "returns table\n" +
+              "(\n" +
+              "    action varchar( 20 ),\n" +
+              "    actionValue int\n" +
+              ")\n" +
+              "language java parameter style derby_jdbc_result_set\n" +
+              "external name 'org.apache.derbyTesting.functionTests.tests.lang.MergeStatementTest.history'\n"
+              );
+        goodStatement
+            ( dboConnection,
+              "create trigger t1_004_del_before\n" +
+              "no cascade before delete on t1_004\n" +
+              "for each statement\n" +
+              "call countRows_004( 't1_004', 'before' )\n"
+              );
+        goodStatement
+            ( dboConnection,
+              "create trigger t1_004_del_after\n" +
+              "after delete on t1_004\n" +
+              "for each statement\n" +
+              "call countRows_004( 't1_004', 'after' )\n"
+              );
+
+        // a statement-trigger-invoking DELETE action without a matching refinement clause
+        vet_004
+            (
+             dboConnection,
+             "merge into t1_004\n" +
+             "using t2_004\n" +
+             "on 2 * t1_004.c2 = 2 * t2_004.c2\n" +
+             "when matched then delete\n",
+             4,
+             new String[][]
+             {
+                 { "3", "30", "300" },   
+             },
+             new String[][]
+             {
+                 { "before", "5" },  
+                 { "after", "1" },   
+             }
+             );
+
+        // a statement-trigger-invoking DELETE action with a matching refinement clause
+        vet_004
+            (
+             dboConnection,
+             "merge into t1_004\n" +
+             "using t2_004\n" +
+             "on 2 * t1_004.c2 = 2 * t2_004.c2\n" +
+             "when matched and c3 = 200 then delete\n",
+             1,
+             new String[][]
+             {
+                 { "1", "10", "100" },  
+                 { "3", "30", "300" },   
+                 { "4", "40", "400" },    
+                 { "5", "50", "500" },    
+             },
+             new String[][]
+             {
+                 { "before", "5" },  
+                 { "after", "4" },   
+             }
+             );
+
+        //
+        // drop schema
+        //
+        goodStatement( dboConnection, "drop trigger t1_004_del_before" );
+        goodStatement( dboConnection, "drop trigger t1_004_del_after" );
+        goodStatement( dboConnection, "drop function history_004" );
+        goodStatement( dboConnection, "drop procedure truncateTriggerHistory_004" );
+        goodStatement( dboConnection, "drop procedure countRows_004" );
+        goodStatement( dboConnection, "drop table t2_004" );
+        goodStatement( dboConnection, "drop table t1_004" );
+    }
+    private void    vet_004
+        (
+         Connection conn,
+         String query,
+         int    rowsAffected,
+         String[][] expectedT1Results,
+         String[][] expectedHistoryResults
+         )
+        throws Exception
+    {
+        vet_004( conn, query, rowsAffected, expectedT1Results, expectedHistoryResults, false );
+        vet_004( conn, query, rowsAffected, expectedT1Results, expectedHistoryResults, true );
+    }
+    private void    vet_004
+        (
+         Connection conn,
+         String query,
+         int    rowsAffected,
+         String[][] expectedT1Results,
+         String[][] expectedHistoryResults,
+         boolean    useHashJoinStrategy
+         )
+        throws Exception
+    {
+        if ( useHashJoinStrategy ) { query = makeHashJoinMerge( query ); }
+
+        populate_004( conn );
+        goodUpdate( conn, query, rowsAffected );
+        assertResults( conn, "select * from t1_004 order by c1", expectedT1Results, false );
+        assertResults( conn, "select * from table( history_004() ) s", expectedHistoryResults, false );
+    }
+    private void    populate_004( Connection conn )
+        throws Exception
+    {
+        goodStatement( conn, "delete from t2_004" );
+        goodStatement( conn, "delete from t1_004" );
+        goodStatement( conn, "call truncateTriggerHistory_004()" );
+
+        goodStatement
+            ( conn,
+              "insert into t1_004( c1, c2, c3 ) values ( 1, 10, 100 ), ( 2, 20, 200 ), ( 3, 30, 300 ), ( 4, 40, 400 ), ( 5, 50, 500 )"
+              );
+        goodStatement
+            ( conn,
+              "insert into t2_004( c2 ) values ( 10 ), ( 20 ), ( 40 ), ( 50 ), ( 60 ), ( 70 )"
+              );
+    }
+    
+    /**
+     * <p>
+     * Test delete action involving before and after row triggers.
+     * </p>
+     */
+    public  void    test_005_deleteActionRowTriggers()
+        throws Exception
+    {
+        Connection  dboConnection = openUserConnection( TEST_DBO );
+
+        //
+        // create schema
+        //
+        goodStatement
+            ( dboConnection,
+              "create table t1_005( c1 int, c2 int, c3 int )" );
+        goodStatement
+            ( dboConnection,
+              "create table t2_005( c1 int generated always as identity, c2 int )" );
+        goodStatement
+            ( dboConnection,
+              "create procedure addHistoryRow_005\n" +
+              "(\n" +
+              "    actionString varchar( 20 ),\n" +
+              "    actionValue int\n" +
+              ")\n" +
+              "language java parameter style java reads sql data\n" +
+              "external name 'org.apache.derbyTesting.functionTests.tests.lang.MergeStatementTest.addHistoryRow'\n"
+              );
+        goodStatement
+            ( dboConnection,
+              "create procedure truncateTriggerHistory_005()\n" +
+              "language java parameter style java no sql\n" +
+              "external name 'org.apache.derbyTesting.functionTests.tests.lang.MergeStatementTest.truncateTriggerHistory'\n"
+              );
+        goodStatement
+            ( dboConnection,
+              "create function history_005()\n" +
+              "returns table\n" +
+              "(\n" +
+              "    action varchar( 20 ),\n" +
+              "    actionValue int\n" +
+              ")\n" +
+              "language java parameter style derby_jdbc_result_set\n" +
+              "external name 'org.apache.derbyTesting.functionTests.tests.lang.MergeStatementTest.history'\n"
+              );
+        goodStatement
+            ( dboConnection,
+              "create trigger t1_005_del_before\n" +
+              "no cascade before delete on t1_005\n" +
+              "referencing old as old\n" +
+              "for each row\n" +
+              "call addHistoryRow_005( 'before', old.c1 )\n" 
+              );
+        goodStatement
+            ( dboConnection,
+              "create trigger t1_005_del_after\n" +
+              "after delete on t1_005\n" +
+              "referencing old as old\n" +
+              "for each row\n" +
+              "call addHistoryRow_005( 'after', old.c1 )\n"
+              );
+        goodStatement
+            ( dboConnection,
+              "insert into t1_005( c1, c2, c3 ) values ( 1, 10, 100 ), ( 2, 20, 200 ), ( 3, 30, 300 ), ( 4, 40, 400 ), ( 5, 50, 500 )"
+              );
+        goodStatement
+            ( dboConnection,
+              "insert into t2_005( c2 ) values ( 10 ), ( 20 ), ( 40 ), ( 50 ), ( 60 ), ( 70 )"
+              );
+
+        // a row-trigger-invoking DELETE action without a matching refinement clause
+        vet_005
+            (
+             dboConnection,
+             "merge into t1_005\n" +
+             "using t2_005\n" +
+             "on 2 * t1_005.c2 = 2 * t2_005.c2\n" +
+             "when matched then delete\n",
+             4,
+             new String[][]
+             {
+                 { "3", "30", "300" },   
+             },
+             new String[][]
+             {
+                 { "before", "1" },  
+                 { "before", "2" },  
+                 { "before", "4" },  
+                 { "before", "5" },  
+                 { "after", "1" },   
+                 { "after", "2" },   
+                 { "after", "4" },   
+                 { "after", "5" },   
+             }
+             );
+
+        // a row-trigger-invoking DELETE action with a matching refinement clause
+        vet_005
+            (
+             dboConnection,
+             "merge into t1_005\n" +
+             "using t2_005\n" +
+             "on 2 * t1_005.c2 = 2 * t2_005.c2\n" +
+             "when matched and c3 = 200 then delete\n",
+             1,
+             new String[][]
+             {
+                 { "1", "10", "100" },  
+                 { "3", "30", "300" },   
+                 { "4", "40", "400" },    
+                 { "5", "50", "500" },    
+             },
+             new String[][]
+             {
+                 { "before", "2" },  
+                 { "after", "2" },   
+             }
+             );
+
+        //
+        // drop schema
+        //
+        goodStatement( dboConnection, "drop trigger t1_005_del_before" );
+        goodStatement( dboConnection, "drop trigger t1_005_del_after" );
+        goodStatement( dboConnection, "drop function history_005" );
+        goodStatement( dboConnection, "drop procedure truncateTriggerHistory_005" );
+        goodStatement( dboConnection, "drop procedure addHistoryRow_005" );
+        goodStatement( dboConnection, "drop table t2_005" );
+        goodStatement( dboConnection, "drop table t1_005" );
+    }
+    private void    vet_005
+        (
+         Connection conn,
+         String query,
+         int    rowsAffected,
+         String[][] expectedT1Results,
+         String[][] expectedHistoryResults
+         )
+        throws Exception
+    {
+        vet_005( conn, query, rowsAffected, expectedT1Results, expectedHistoryResults, false );
+        vet_005( conn, query, rowsAffected, expectedT1Results, expectedHistoryResults, true );
+    }
+    private void    vet_005
+        (
+         Connection conn,
+         String query,
+         int    rowsAffected,
+         String[][] expectedT1Results,
+         String[][] expectedHistoryResults,
+         boolean    useHashJoinStrategy
+         )
+        throws Exception
+    {
+        if ( useHashJoinStrategy ) { query = makeHashJoinMerge( query ); }
+
+        populate_005( conn );
+        goodUpdate( conn, query, rowsAffected );
+        assertResults( conn, "select * from t1_005 order by c1", expectedT1Results, false );
+        assertResults( conn, "select * from table( history_005() ) s", expectedHistoryResults, false );
+    }
+    private void    populate_005( Connection conn )
+        throws Exception
+    {
+        goodStatement( conn, "delete from t2_005" );
+        goodStatement( conn, "delete from t1_005" );
+        goodStatement( conn, "call truncateTriggerHistory_005()" );
+
+        goodStatement
+            ( conn,
+              "insert into t1_005( c1, c2, c3 ) values ( 1, 10, 100 ), ( 2, 20, 200 ), ( 3, 30, 300 ), ( 4, 40, 400 ), ( 5, 50, 500 )"
+              );
+        goodStatement
+            ( conn,
+              "insert into t2_005( c2 ) values ( 10 ), ( 20 ), ( 40 ), ( 50 ), ( 60 ), ( 70 )"
+              );
+    }
+    
     ///////////////////////////////////////////////////////////////////////////////////
     //
     // ROUTINES
@@ -569,9 +1179,84 @@ public class MergeStatementTest extends 
         return 1;
     }
 
+    /** Procedure to truncation the table which records trigger actions */
+    public  static  void    truncateTriggerHistory()
+    {
+        _triggerHistory.clear();
+    }
+
+    /** Table function for listing the contents of the trigger record */
+    public  static  ResultSet   history()
+    {
+        String[][]  rows = new String[ _triggerHistory.size() ][];
+        _triggerHistory.toArray( rows );
+
+        return new StringArrayVTI( TRIGGER_HISTORY_COLUMNS, rows );
+    }
+
+    /**
+     * <p>
+     * Trigger-called procedure for counting rows in a candidate table and then inserting
+     * the result in a history table. The history table has the following shape:
+     * </p>
+     *
+     * <ul>
+     * <li>id</li>
+     * <li>actionString</li>
+     * <li>rowCount</li>
+     * </ul>
+     */
+    public  static  void    countRows
+        ( String candidateName, String actionString )
+        throws SQLException
+    {
+        Connection  conn = getNestedConnection();
+        
+        String  selectCount = "select count(*) from " + candidateName;
+        ResultSet   selectRS = conn.prepareStatement( selectCount ).executeQuery();
+        selectRS.next();
+        int rowCount = selectRS.getInt( 1 );
+        selectRS.close();
+
+        addHistoryRow( actionString, rowCount );
+    }
+
+    /** Procedure for adding trigger history */
+    public  static  void    addHistoryRow( String actionString, int actionValue )
+    {
+        _triggerHistory.add( new String[] { actionString, Integer.toString( actionValue ) } );
+    }
+
     public  static  Connection  getNestedConnection()   throws SQLException
     {
         return DriverManager.getConnection( "jdbc:default:connection" );
     }
 
+    ///////////////////////////////////////////////////////////////////////////////////
+    //
+    // MINIONS
+    //
+    ///////////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * <p>
+     * Convert a MERGE statement which uses a nested join strategy
+     * into an equivalent MERGE statement which uses a hash join
+     * strategy. To do this, we replace the ON clause with an equivalent
+     * ON clause which joins on key columns instead of expressions.
+     * </p>
+     *
+     * <p>
+     * The original query is a MERGE statement whose ON clauses joins
+     * complex expressions, making the optimizer choose a nested-loop
+     * strategy. This method transforms the MERGE statement into one
+     * whose ON clause joins simple keys. This will make the optimizer
+     * choose a hash-join strategy.
+     * </p>
+     */
+    private String  makeHashJoinMerge( String original )
+    {
+        return original.replace ( "2 *", " " );
+    }
+    
 }