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 *", " " );
+ }
+
}