blob: 336c77ae263e5f2c278750c103b52bee1adcd6ab [file] [log] [blame]
/*
Derby - Class org.apache.derby.impl.sql.compile.MergeNode
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.compile;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import org.apache.derby.iapi.error.StandardException;
import org.apache.derby.iapi.reference.ClassName;
import org.apache.derby.iapi.reference.SQLState;
import org.apache.derby.iapi.services.classfile.VMOpcode;
import org.apache.derby.iapi.services.compiler.MethodBuilder;
import org.apache.derby.iapi.services.context.ContextManager;
import org.apache.derby.iapi.sql.compile.CompilerContext;
import org.apache.derby.iapi.sql.compile.IgnoreFilter;
import org.apache.derby.iapi.sql.compile.Visitor;
import org.apache.derby.iapi.sql.conn.Authorizer;
import org.apache.derby.iapi.sql.dictionary.ColumnDescriptor;
import org.apache.derby.iapi.sql.dictionary.DataDictionary;
import org.apache.derby.iapi.sql.dictionary.TableDescriptor;
import org.apache.derby.iapi.sql.execute.ConstantAction;
import org.apache.derby.iapi.util.IdUtil;
import org.apache.derby.shared.common.sanity.SanityManager;
/**
* <p>
* A MergeNode represents a MERGE statement. The statement looks like
* this...
* </p>
*
* <pre>
* MERGE INTO targetTable
* USING sourceTable
* ON searchCondition
* matchingClause1 ... matchingClauseN
* </pre>
*
* <p>
* ...where each matching clause looks like this...
* </p>
*
* <pre>
* WHEN MATCHED [ AND matchingRefinement ] THEN DELETE
* </pre>
*
* <p>
* ...or
* </p>
*
* <pre>
* WHEN MATCHED [ AND matchingRefinement ] THEN UPDATE SET col1 = expr1, ... colM = exprM
* </pre>
*
* <p>
* ...or
* </p>
*
* <pre>
* WHEN NOT MATCHED [ AND matchingRefinement ] THEN INSERT columnList VALUES valueList
* </pre>
*
* <p>
* The Derby compiler essentially rewrites this statement into a driving left join
* followed by a series of DELETE/UPDATE/INSERT actions. The left join looks like
* this:
* </p>
*
* <pre>
* SELECT selectList FROM sourceTable LEFT OUTER JOIN targetTable ON searchCondition
* </pre>
*
* <p>
* The selectList of the driving left join consists of the following:
* </p>
*
* <ul>
* <li>All of the columns mentioned in the searchCondition.</li>
* <li>All of the columns mentioned in the matchingRefinement clauses.</li>
* <li>All of the columns mentioned in the SET clauses and the INSERT columnLists and valueLists.</li>
* <li>All additional columns needed for the triggers and foreign keys fired by the DeleteResultSets
* and UpdateResultSets constructed for the WHEN MATCHED clauses.</li>
* <li>All additional columns needed to build index rows and evaluate generated columns
* needed by the UpdateResultSets constructed for the WHEN MATCHED...THEN UPDATE clauses.</li>
* <li>A trailing targetTable.RowLocation column.</li>
* </ul>
*
* <p>
* The driving left join's selectList then looks like this...
* </p>
*
* <pre>
* sc1, ..., scN, tc1, ..., tcM, targetTable.RowLocation
* </pre>
*
* <p>
* Where sc1...scN are the columns we need from the source table (in alphabetical
* order) and tc1...tcM are the columns we need from the target table (in alphabetical
* order).
* </p>
*
* <p>
* The matchingRefinement expressions are bound and generated against the
* FromList of the driving left join. Dummy DeleteNode, UpdateNode, and InsertNode
* statements are independently constructed in order to bind and generate the DELETE/UPDATE/INSERT
* actions.
* </p>
*
* <p>
* At execution time, the targetTable.RowLocation column is used to determine
* whether a given driving row matches. The row matches iff targetTable.RowLocation is not null.
* The driving row is then assigned to the
* first DELETE/UPDATE/INSERT action to which it applies. The relevant columns from
* the driving row are extracted and buffered in a temporary table (the "then" rows) specific to that
* DELETE/UPDATE/INSERT action. After the driving left join has been processed,
* the DELETE/UPDATE/INSERT actions are run in order, each taking its corresponding
* temporary table as its source ResultSet.
* </p>
*
* <p>
* Name resolution was a particularly thorny problem. This is because name resolution
* behaves differently for SELECTs and UPDATEs. In particular, while processing UPDATEs,
* the compiler throws away name resolution information; this happens as a consequence
* of work done on DERBY-1043. In the end, I had to invent more name resolution machinery
* in order to compensate for the differences in the handling of SELECTs and UPDATEs.
* If we are to allow subqueries in matching refinement clauses and in the values expressions
* of INSERT and UPDATE actions, then we probably need to remove this special name
* resolution machinery. And that, in turn, probably means revisiting DERBY-1043.
* </p>
*
* <p>
* The special name resolution machinery involves marking source and target column references
* in order to make it clear which table they belong to. This is done in associateColumn(). The markers
* are consulted at code-generation time in order to resolve column references when we
* generate the expressions needed to populate the rows which go into the temporary tables.
* That resolution happens in MatchingClauseNode.getSelectListOffset().
* </p>
*/
public final class MergeNode extends DMLModStatementNode
{
///////////////////////////////////////////////////////////////////////////////////
//
// CONSTANTS
//
///////////////////////////////////////////////////////////////////////////////////
public static final int SOURCE_TABLE_INDEX = 0;
public static final int TARGET_TABLE_INDEX = 1;
private static final String TARGET_ROW_LOCATION_NAME = "###TargetRowLocation";
///////////////////////////////////////////////////////////////////////////////////
//
// STATE
//
///////////////////////////////////////////////////////////////////////////////////
//
// Filled in by the constructor.
//
private FromBaseTable _targetTable;
private FromTable _sourceTable;
private ValueNode _searchCondition;
private QueryTreeNodeVector<MatchingClauseNode> _matchingClauses;
//
// Filled in at bind() time.
//
private ResultColumnList _selectList;
private FromList _leftJoinFromList;
private HalfOuterJoinNode _hojn;
//
// Filled in at generate() time.
//
private ConstantAction _constantAction;
private CursorNode _leftJoinCursor;
///////////////////////////////////////////////////////////////////////////////////
//
// CONSTRUCTOR
//
///////////////////////////////////////////////////////////////////////////////////
/**
* <p>
* Constructor for a MergeNode.
* </p>
*/
public MergeNode
(
FromTable targetTable,
FromTable sourceTable,
ValueNode searchCondition,
QueryTreeNodeVector<MatchingClauseNode> matchingClauses,
ContextManager cm
)
throws StandardException
{
super( null, null, cm );
if ( !( targetTable instanceof FromBaseTable) ) { notBaseTable(); }
else { _targetTable = (FromBaseTable) targetTable; }
_sourceTable = sourceTable;
_searchCondition = searchCondition;
_matchingClauses = matchingClauses;
}
///////////////////////////////////////////////////////////////////////////////////
//
// BIND-TIME ENTRY POINTS CALLED BY MatchingClauseNode
//
///////////////////////////////////////////////////////////////////////////////////
/** Get the target table for the MERGE statement */
FromBaseTable getTargetTable() { return _targetTable; }
/**
* <p>
* Associate a column with the SOURCE or TARGET table. This is
* part of the special name resolution machinery which smooths over
* the differences between name resolution for SELECTs and UPDATEs.
* </p>
*/
void associateColumn( FromList fromList, ColumnReference cr, int mergeTableID )
throws StandardException
{
if ( mergeTableID != ColumnReference.MERGE_UNKNOWN ) { cr.setMergeTableID( mergeTableID ); }
else
{
// we have to figure out which table the column is in
String columnsTableName = cr.getTableName();
if ( ((FromTable) fromList.elementAt( SOURCE_TABLE_INDEX )).getMatchingColumn( cr ) != null )
{
cr.setMergeTableID( ColumnReference.MERGE_SOURCE );
}
else if ( ((FromTable) fromList.elementAt( TARGET_TABLE_INDEX )).getMatchingColumn( cr ) != null )
{
cr.setMergeTableID( ColumnReference.MERGE_TARGET );
}
}
// Don't raise an error if a column in another table is referenced and we
// don't know how to handle it here. If the column is not in the SOURCE or TARGET
// table, then it will be caught by other bind-time logic. Columns which ought
// to be associated, but aren't, will be caught later on by MatchingClauseNode.getMergeTableID().
}
/** Boilerplate for binding an expression against a FromList */
void bindExpression( ValueNode value, FromList fromList )
throws StandardException
{
CompilerContext cc = getCompilerContext();
final int previousReliability = cc.getReliability();
cc.setReliability( previousReliability | CompilerContext.SQL_IN_ROUTINES_ILLEGAL );
cc.pushCurrentPrivType( Authorizer.SELECT_PRIV );
try {
// this adds SELECT priv on referenced columns and EXECUTE privs on referenced routines
value.bindExpression
(
fromList,
new SubqueryList( getContextManager() ),
new ArrayList<AggregateNode>()
);
}
finally
{
// Restore previous compiler state
cc.popCurrentPrivType();
cc.setReliability( previousReliability );
}
}
/**
* <p>
* Add the columns in the matchingRefinement clause to the evolving map.
* This is called when we're building the SELECT list for the driving left join.
* </p>
*/
void getColumnsInExpression
( HashMap<String,ColumnReference> map, ValueNode expression, int mergeTableID )
throws StandardException
{
if ( expression == null ) { return; }
List<ColumnReference> colRefs = getColumnReferences( expression );
getColumnsFromList( map, colRefs, mergeTableID );
}
/**
* <p>
* Add a list of columns to the the evolving map.
* This is called when we're building the SELECT list for the driving left join.
* </p>
*/
void getColumnsFromList
( HashMap<String,ColumnReference> map, ResultColumnList rcl, int mergeTableID )
throws StandardException
{
List<ColumnReference> colRefs = getColumnReferences( rcl );
getColumnsFromList( map, colRefs, mergeTableID );
}
///////////////////////////////////////////////////////////////////////////////////
//
// bind() BEHAVIOR
//
///////////////////////////////////////////////////////////////////////////////////
@Override
public void bindStatement() throws StandardException
{
DataDictionary dd = getDataDictionary();
// source table must be a vti or base table
if (
!(_sourceTable instanceof FromVTI) &&
!(_sourceTable instanceof FromBaseTable)
)
{
throw StandardException.newException( SQLState.LANG_SOURCE_NOT_BASE_OR_VTI );
}
// source and target may not have the same correlation names
if ( getExposedName( _targetTable ).equals( getExposedName( _sourceTable ) ) )
{
throw StandardException.newException( SQLState.LANG_SAME_EXPOSED_NAME );
}
// don't allow derived column lists right now
forbidDerivedColumnLists();
// synonyms not allowed
forbidSynonyms();
//
// Don't add any privileges until we bind the matching clauses.
//
IgnoreFilter ignorePermissions = new IgnoreFilter();
getCompilerContext().addPrivilegeFilter( ignorePermissions );
FromList dfl = new FromList( getContextManager() );
FromTable dflSource = cloneFromTable( _sourceTable );
FromBaseTable dflTarget = (FromBaseTable) cloneFromTable( _targetTable );
dfl.addFromTable( dflSource );
dfl.addFromTable( dflTarget );
dfl.bindTables( dd, new FromList( getOptimizerFactory().doJoinOrderOptimization(), getContextManager() ) );
// target table must be a base table
if ( !targetIsBaseTable( dflTarget ) ) { notBaseTable(); }
// ready to add permissions
getCompilerContext().removePrivilegeFilter( ignorePermissions );
for ( MatchingClauseNode mcn : _matchingClauses )
{
FromList dummyFromList = cloneFromList( dd, dflTarget );
FromBaseTable dummyTargetTable = (FromBaseTable) dummyFromList.elementAt( TARGET_TABLE_INDEX );
mcn.bind( dd, this, dummyFromList, dummyTargetTable );
// window function not allowed
SelectNode.checkNoWindowFunctions(mcn, "matching clause");
// aggregates not allowed
checkNoAggregates(mcn);
}
bindLeftJoin( dd );
}
static void checkNoAggregates(QueryTreeNode clause)
throws StandardException {
// Clause cannot contain window aggregates except inside subqueries
HasNodeVisitor visitor = new HasNodeVisitor(AggregateNode.class,
SubqueryNode.class);
clause.accept(visitor);
if (visitor.hasNode()) {
throw StandardException.newException(
SQLState.LANG_NO_AGGREGATES_IN_MERGE_MATCHING_CLAUSE);
}
}
/////////////////////////////////////
//
// TABLE AND CORRELATION CHECKS
//
/////////////////////////////////////
/** Get the exposed name of a FromTable */
private String getExposedName( FromTable ft ) throws StandardException
{
return ft.getTableName().getTableName();
}
@Override
public boolean referencesSessionSchema() throws StandardException {
return _sourceTable.referencesSessionSchema()
|| _targetTable.referencesSessionSchema()
|| _searchCondition.referencesSessionSchema()
|| _matchingClauses.referencesSessionSchema();
}
/**
*<p>
* Because of name resolution complexities, we do not allow derived column lists
* on source or target tables. These lists arise in queries like the following:
* </p>
*
* <pre>
* merge into t1 r( x )
* using t2 on r.x = t2.a
* when matched then delete;
*
* merge into t1
* using t2 r( x ) on t1.a = r.x
* when matched then delete;
* </pre>
*/
private void forbidDerivedColumnLists() throws StandardException
{
if ( (_sourceTable.getResultColumns() != null) || (_targetTable.getResultColumns() != null) )
{
throw StandardException.newException( SQLState.LANG_NO_DCL_IN_MERGE );
}
}
/** Neither the source nor the target table may be a synonym */
private void forbidSynonyms() throws StandardException
{
forbidSynonyms(_targetTable.getTableNameField().cloneMe());
if ( _sourceTable instanceof FromBaseTable )
{
forbidSynonyms(
((FromBaseTable) _sourceTable).getTableNameField().cloneMe());
}
}
private void forbidSynonyms(TableName tableName) throws StandardException
{
tableName.bind();
TableName synonym = resolveTableToSynonym( tableName );
if ( synonym != null )
{
throw StandardException.newException( SQLState.LANG_NO_SYNONYMS_IN_MERGE );
}
}
/** Throw a "not base table" exception */
private void notBaseTable() throws StandardException
{
throw StandardException.newException( SQLState.LANG_TARGET_NOT_BASE_TABLE );
}
/** Return true if the target table is a base table */
private boolean targetIsBaseTable( FromBaseTable targetTable ) throws StandardException
{
FromBaseTable fbt = targetTable;
TableDescriptor desc = fbt.getTableDescriptor();
if ( desc == null ) { return false; }
switch( desc.getTableType() )
{
case TableDescriptor.BASE_TABLE_TYPE:
case TableDescriptor.GLOBAL_TEMPORARY_TABLE_TYPE:
return true;
default:
return false;
}
}
/** Return true if the source table is a base table, view, or table function */
private boolean sourceIsBase_or_VTI() throws StandardException
{
if ( _sourceTable instanceof FromVTI ) { return true; }
if ( !( _sourceTable instanceof FromBaseTable) ) { return false; }
FromBaseTable fbt = (FromBaseTable) _sourceTable;
TableDescriptor desc = fbt.getTableDescriptor();
if ( desc == null ) { return false; }
switch( desc.getTableType() )
{
case TableDescriptor.BASE_TABLE_TYPE:
case TableDescriptor.SYSTEM_TABLE_TYPE:
case TableDescriptor.GLOBAL_TEMPORARY_TABLE_TYPE:
return true;
default:
return false;
}
}
///////////////////////////
//
// BINDING THE LEFT JOIN
//
///////////////////////////
/**
* Bind the driving left join select.
* Stuffs the left join SelectNode into the resultSet variable.
*/
private void bindLeftJoin( DataDictionary dd ) throws StandardException
{
CompilerContext cc = getCompilerContext();
final int previousReliability = cc.getReliability();
try {
cc.setReliability( previousReliability | CompilerContext.SQL_IN_ROUTINES_ILLEGAL );
//
// Don't add any privileges until we bind the matching refinement clauses.
//
IgnoreFilter ignorePermissions = new IgnoreFilter();
getCompilerContext().addPrivilegeFilter( ignorePermissions );
_hojn = new HalfOuterJoinNode
(
_sourceTable,
_targetTable,
_searchCondition,
null,
false,
null,
getContextManager()
);
_leftJoinFromList = _hojn.makeFromList( true, true );
_leftJoinFromList.bindTables( dd, new FromList( getOptimizerFactory().doJoinOrderOptimization(), getContextManager() ) );
if ( !sourceIsBase_or_VTI() )
{
throw StandardException.newException( SQLState.LANG_SOURCE_NOT_BASE_OR_VTI );
}
FromList topFromList = new FromList( getOptimizerFactory().doJoinOrderOptimization(), getContextManager() );
topFromList.addFromTable( _hojn );
// ready to add permissions
getCompilerContext().removePrivilegeFilter( ignorePermissions );
// preliminary binding of the matching clauses to resolve column
// references. this ensures that we can add all of the columns from
// the matching refinements to the SELECT list of the left join.
// we re-bind the matching clauses when we're done binding the left join
// because, at that time, we have result set numbers needed for
// code generation.
for ( MatchingClauseNode mcn : _matchingClauses )
{
mcn.bindRefinement( this, _leftJoinFromList );
}
ResultColumnList selectList = buildSelectList();
// save a copy so that we can remap column references when generating the temporary rows
_selectList = selectList.copyListAndObjects();
resultSet = new SelectNode
(
selectList,
topFromList,
null, // where clause
null, // group by list
null, // having clause
null, // window list
null, // optimizer plan override
getContextManager()
);
// Wrap the SELECT in a CursorNode in order to finish binding it.
_leftJoinCursor = new CursorNode
(
"SELECT",
resultSet,
null,
null,
null,
null,
false,
CursorNode.READ_ONLY,
null,
true,
getContextManager()
);
//
// We're only interested in privileges related to the ON clause.
// Otherwise, the driving left join should not contribute any
// privilege requirements.
//
getCompilerContext().addPrivilegeFilter( ignorePermissions );
_leftJoinCursor.bindStatement();
// ready to add permissions again
getCompilerContext().removePrivilegeFilter( ignorePermissions );
// now figure out what privileges are needed for the ON clause
addOnClausePrivileges();
}
finally
{
// Restore previous compiler state
cc.setReliability( previousReliability );
}
}
////////////////////////////
//
// CLONING THE FROM LIST
//
////////////////////////////
/** Create a FromList for binding a WHEN [ NOT ] MATCHED clause */
private FromList cloneFromList( DataDictionary dd, FromBaseTable targetTable )
throws StandardException
{
FromList dummyFromList = new FromList( getContextManager() );
FromBaseTable dummyTargetTable = new FromBaseTable
(
targetTable.getTableNameField(),
targetTable.correlationName,
null,
null,
getContextManager()
);
FromTable dummySourceTable = cloneFromTable( _sourceTable );
dummyTargetTable.setMergeTableID( ColumnReference.MERGE_TARGET );
dummySourceTable.setMergeTableID ( ColumnReference.MERGE_SOURCE );
dummyFromList.addFromTable( dummySourceTable );
dummyFromList.addFromTable( dummyTargetTable );
//
// Don't add any privileges while binding the tables.
//
IgnoreFilter ignorePermissions = new IgnoreFilter();
getCompilerContext().addPrivilegeFilter( ignorePermissions );
dummyFromList.bindTables( dd, new FromList( getOptimizerFactory().doJoinOrderOptimization(), getContextManager() ) );
// ready to add permissions
getCompilerContext().removePrivilegeFilter( ignorePermissions );
return dummyFromList;
}
/** Clone a FromTable to avoid binding the original */
private FromTable cloneFromTable( FromTable fromTable ) throws StandardException
{
if ( fromTable instanceof FromVTI )
{
FromVTI source = (FromVTI) fromTable;
return new FromVTI
(
source.methodCall,
source.correlationName,
source.getResultColumns(),
null,
source.exposedName,
getContextManager()
);
}
else if ( fromTable instanceof FromBaseTable )
{
FromBaseTable source = (FromBaseTable) fromTable;
return new FromBaseTable
(
source.tableName,
source.correlationName,
null,
null,
getContextManager()
);
}
else
{
throw StandardException.newException( SQLState.LANG_SOURCE_NOT_BASE_OR_VTI );
}
}
///////////////////////////
//
// PRIVILEGE MANAGEMENT
//
///////////////////////////
/**
* <p>
* Add the privileges required by the ON clause.
* </p>
*/
private void addOnClausePrivileges() throws StandardException
{
// add SELECT privilege on columns
for ( ColumnReference cr : getColumnReferences( _searchCondition ) )
{
addColumnPrivilege( cr );
}
// add EXECUTE privilege on routines
for ( StaticMethodCallNode routine : getRoutineReferences( _searchCondition ) )
{
addRoutinePrivilege( routine );
}
// add USAGE privilege on CASTs to user-defined types
for ( CastNode value : getCastNodes( _searchCondition ) )
{
addUDTUsagePriv( value );
}
}
/**
* <p>
* Add SELECT privilege on the indicated column.
* </p>
*/
private void addColumnPrivilege( ColumnReference cr )
throws StandardException
{
CompilerContext cc = getCompilerContext();
ResultColumn rc = cr.getSource();
if ( rc != null )
{
ColumnDescriptor colDesc = rc.getColumnDescriptor();
if ( colDesc != null )
{
cc.pushCurrentPrivType( Authorizer.SELECT_PRIV );
cc.addRequiredColumnPriv( colDesc );
cc.popCurrentPrivType();
}
}
}
/**
* <p>
* Add EXECUTE privilege on the indicated routine.
* </p>
*/
private void addRoutinePrivilege( StaticMethodCallNode routine )
throws StandardException
{
CompilerContext cc = getCompilerContext();
cc.pushCurrentPrivType( Authorizer.EXECUTE_PRIV );
cc.addRequiredRoutinePriv( routine.ad );
cc.popCurrentPrivType();
}
/** Get a list of CastNodes in an expression */
private List<CastNode> getCastNodes( QueryTreeNode expression )
throws StandardException
{
CollectNodesVisitor<CastNode> getCNs =
new CollectNodesVisitor<CastNode>(CastNode.class);
expression.accept(getCNs);
return getCNs.getList();
}
/** Get a list of routines in an expression */
private List<StaticMethodCallNode> getRoutineReferences( QueryTreeNode expression )
throws StandardException
{
CollectNodesVisitor<StaticMethodCallNode> getSMCNs =
new CollectNodesVisitor<StaticMethodCallNode>(StaticMethodCallNode.class);
expression.accept(getSMCNs);
return getSMCNs.getList();
}
///////////////////////////////
//
// BUILD THE SELECT LIST
// FOR THE DRIVING LEFT JOIN.
//
///////////////////////////////
/** Build the select list for the left join */
private ResultColumnList buildSelectList() throws StandardException
{
HashMap<String,ColumnReference> drivingColumnMap = new HashMap<String,ColumnReference>();
getColumnsInExpression( drivingColumnMap, _searchCondition, ColumnReference.MERGE_UNKNOWN );
for ( MatchingClauseNode mcn : _matchingClauses )
{
mcn.getColumnsInExpressions( this, drivingColumnMap );
int mergeTableID = mcn.isDeleteClause() ? ColumnReference.MERGE_TARGET : ColumnReference.MERGE_UNKNOWN;
getColumnsFromList( drivingColumnMap, mcn.getThenColumns(), mergeTableID );
}
ResultColumnList selectList = new ResultColumnList( getContextManager() );
// add all of the columns from the source table which are mentioned
addColumns
(
(FromTable) _leftJoinFromList.elementAt( SOURCE_TABLE_INDEX ),
drivingColumnMap,
selectList,
ColumnReference.MERGE_SOURCE
);
// add all of the columns from the target table which are mentioned
addColumns
(
(FromTable) _leftJoinFromList.elementAt( TARGET_TABLE_INDEX ),
drivingColumnMap,
selectList,
ColumnReference.MERGE_TARGET
);
addTargetRowLocation( selectList );
return selectList;
}
/** Add the target table's row location to the left join's select list */
private void addTargetRowLocation( ResultColumnList selectList )
throws StandardException
{
// tell the target table to generate a row location column
_targetTable.setRowLocationColumnName( TARGET_ROW_LOCATION_NAME );
TableName fromTableName = _targetTable.getTableName();
ColumnReference cr = new ColumnReference
( TARGET_ROW_LOCATION_NAME, fromTableName, getContextManager() );
cr.setMergeTableID( ColumnReference.MERGE_TARGET );
ResultColumn rowLocationColumn = new ResultColumn( (String) null, cr, getContextManager() );
rowLocationColumn.markGenerated();
selectList.addResultColumn( rowLocationColumn );
}
/**
* <p>
* Add to an evolving select list the columns from the indicated table.
* </p>
*/
private void addColumns
(
FromTable fromTable,
HashMap<String,ColumnReference> drivingColumnMap,
ResultColumnList selectList,
int mergeTableID
)
throws StandardException
{
String[] columnNames = getColumns( mergeTableID, drivingColumnMap );
TableName tableName = fromTable.getTableName();
for ( int i = 0; i < columnNames.length; i++ )
{
ColumnReference cr = new ColumnReference
( columnNames[ i ], tableName, getContextManager() );
cr.setMergeTableID( mergeTableID );
ResultColumn rc = new ResultColumn( (String) null, cr, getContextManager() );
selectList.addResultColumn( rc );
}
}
/** Get the column names from the table with the given table number, in sorted order */
private String[] getColumns( int mergeTableID, HashMap<String,ColumnReference> map )
{
HashSet<String> set = new HashSet<String>();
for ( ColumnReference cr : map.values() )
{
if ( cr.getMergeTableID() == mergeTableID ) { set.add( cr.getColumnName() ); }
}
String[] retval = new String[ set.size() ];
set.toArray( retval );
Arrays.sort( retval );
return retval;
}
/** Get a list of column references in an expression */
private List<ColumnReference> getColumnReferences( QueryTreeNode expression )
throws StandardException
{
CollectNodesVisitor<ColumnReference> getCRs =
new CollectNodesVisitor<ColumnReference>(ColumnReference.class);
expression.accept(getCRs);
return getCRs.getList();
}
/** Add a list of columns to the the evolving map */
private void getColumnsFromList
( HashMap<String,ColumnReference> map, List<ColumnReference> colRefs, int mergeTableID )
throws StandardException
{
for ( ColumnReference cr : colRefs )
{
addColumn( map, cr, mergeTableID );
}
}
/** Add a column to the evolving map of referenced columns */
void addColumn
(
HashMap<String,ColumnReference> map,
ColumnReference cr,
int mergeTableID
)
throws StandardException
{
if ( cr.getTableName() == null )
{
cr = cr.bindExpression(
_leftJoinFromList,
new SubqueryList(getContextManager()),
new ArrayList<AggregateNode>());
TableName tableName = cr.getQualifiedTableName();
cr = new ColumnReference( cr.getColumnName(), tableName, getContextManager() );
}
associateColumn( _leftJoinFromList, cr, mergeTableID );
String key = makeDCMKey( cr.getTableName(), cr.getColumnName() );
ColumnReference mapCR = map.get( key );
if ( mapCR != null )
{
mapCR.setMergeTableID( cr.getMergeTableID() );
}
else
{
map.put( key, cr );
}
}
/** Make a HashMap key for a column in the driving column map of the LEFT JOIN */
private String makeDCMKey( String tableName, String columnName )
{
return IdUtil.mkQualifiedName( tableName, columnName );
}
///////////////////////////////////////////////////////////////////////////////////
//
// optimize() BEHAVIOR
//
///////////////////////////////////////////////////////////////////////////////////
@Override
public void optimizeStatement() throws StandardException
{
//
// Don't add any privileges during optimization.
//
IgnoreFilter ignorePermissions = new IgnoreFilter();
getCompilerContext().addPrivilegeFilter( ignorePermissions );
/* First optimize the left join */
_leftJoinCursor.optimizeStatement();
//
// No need to set lockMode in the master MergeNode. The individual
// actions and the driving left-join will set their own lock modes.
//
// now optimize the INSERT/UPDATE/DELETE actions
for ( MatchingClauseNode mcn : _matchingClauses )
{
mcn.optimize();
}
// ready to add permissions again
getCompilerContext().removePrivilegeFilter( ignorePermissions );
}
///////////////////////////////////////////////////////////////////////////////////
//
// generate() BEHAVIOR
//
///////////////////////////////////////////////////////////////////////////////////
@Override
void generate( ActivationClassBuilder acb, MethodBuilder mb )
throws StandardException
{
int clauseCount = _matchingClauses.size();
/* generate the parameters */
generateParameterValueSet(acb);
acb.pushGetResultSetFactoryExpression( mb );
// arg 1: the driving left join
_leftJoinCursor.generate( acb, mb );
// dig up the actual result set which was generated and which will drive the MergeResultSet
ScrollInsensitiveResultSetNode sirs = (ScrollInsensitiveResultSetNode) _leftJoinCursor.resultSet;
ResultSetNode generatedScan = sirs.getChildResult();
ConstantAction[] clauseActions = new ConstantAction[ clauseCount ];
for ( int i = 0; i < clauseCount; i++ )
{
MatchingClauseNode mcn = _matchingClauses.elementAt(i);
mcn.generate( acb, _selectList, generatedScan, _hojn, i );
clauseActions[ i ] = mcn.makeConstantAction( acb );
}
_constantAction = getGenericConstantActionFactory().getMergeConstantAction( clauseActions );
mb.callMethod
( VMOpcode.INVOKEINTERFACE, (String) null, "getMergeResultSet", ClassName.ResultSet, 1 );
}
@Override
public ConstantAction makeConstantAction() throws StandardException
{
return _constantAction;
}
///////////////////////////////////////////////////////////////////////////////////
//
// Visitable BEHAVIOR
//
///////////////////////////////////////////////////////////////////////////////////
/**
* Accept the visitor for all visitable children of this node.
*
* @param v the visitor
*
* @exception StandardException on error
*/
@Override
void acceptChildren(Visitor v)
throws StandardException
{
if ( _leftJoinCursor != null )
{
_leftJoinCursor.acceptChildren( v );
}
else
{
super.acceptChildren( v );
_targetTable.accept( v );
_sourceTable.accept( v );
_searchCondition.accept( v );
}
for ( MatchingClauseNode mcn : _matchingClauses )
{
mcn.accept( v );
}
}
/**
* Prints the sub-nodes of this object. See QueryTreeNode.java for
* how tree printing is supposed to work.
*
* @param depth The depth of this node in the tree
*/
@Override
void printSubNodes( int depth )
{
if (SanityManager.DEBUG)
{
super.printSubNodes( depth );
printLabel( depth, "targetTable: " );
_targetTable.treePrint( depth + 1 );
printLabel( depth, "sourceTable: " );
_sourceTable.treePrint( depth + 1 );
if ( _searchCondition != null )
{
printLabel( depth, "searchCondition: " );
_searchCondition.treePrint( depth + 1 );
}
for ( MatchingClauseNode mcn : _matchingClauses )
{
printLabel( depth, mcn.toString() );
mcn.treePrint( depth + 1 );
}
}
}
@Override
String statementToString()
{
return "MERGE";
}
}