Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/**
This file is part of the Zeidon Java Object Engine (Zeidon JOE).
Zeidon JOE is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Zeidon JOE is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with Zeidon JOE. If not, see .
Copyright 2009-2015 QuinSoft
*/
package com.quinsoft.zeidon.dbhandler;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.CharSetUtils;
import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.quinsoft.zeidon.AbstractOptionsConfiguration;
import com.quinsoft.zeidon.ActivateFlags;
import com.quinsoft.zeidon.ActivateOptions;
import com.quinsoft.zeidon.ActivateOptions.ActivateOrder;
import com.quinsoft.zeidon.Application;
import com.quinsoft.zeidon.AttributeInstance;
import com.quinsoft.zeidon.CreateEntityFlags;
import com.quinsoft.zeidon.CursorPosition;
import com.quinsoft.zeidon.CursorResult;
import com.quinsoft.zeidon.EntityCache;
import com.quinsoft.zeidon.EntityCursor;
import com.quinsoft.zeidon.EntityInstance;
import com.quinsoft.zeidon.GenKeyHandler;
import com.quinsoft.zeidon.IncludeFlags;
import com.quinsoft.zeidon.ObjectEngine;
import com.quinsoft.zeidon.Pagination;
import com.quinsoft.zeidon.Task;
import com.quinsoft.zeidon.View;
import com.quinsoft.zeidon.ZeidonException;
import com.quinsoft.zeidon.domains.Domain;
import com.quinsoft.zeidon.objectdefinition.AttributeDef;
import com.quinsoft.zeidon.objectdefinition.DataField;
import com.quinsoft.zeidon.objectdefinition.DataRecord;
import com.quinsoft.zeidon.objectdefinition.EntityDef;
import com.quinsoft.zeidon.objectdefinition.LockingLevel;
import com.quinsoft.zeidon.objectdefinition.LodDef;
import com.quinsoft.zeidon.objectdefinition.RelField;
import com.quinsoft.zeidon.objectdefinition.RelRecord;
import com.quinsoft.zeidon.objectdefinition.RelRecord.RelationshipType;
import com.quinsoft.zeidon.standardoe.NoOpPessimisticLockingHandler;
import com.quinsoft.zeidon.standardoe.OiRelinker;
/**
* @author DG
*
*/
public abstract class AbstractSqlHandler implements DbHandler, GenKeyHandler
{
public static final PessimisticLockingHandler NOOP_PESSIMISTIC_LOCKING_HANDLER = new NoOpPessimisticLockingHandler();
/**
* These are the flags to use when creating an entity. It prevents some
* normal processing for occuring that we don't need when activating.
*/
protected static final EnumSet CREATE_FLAGS = EnumSet.of( CreateEntityFlags.fNO_SPAWNING,
CreateEntityFlags.fIGNORE_MAX_CARDINALITY,
CreateEntityFlags.fDONT_UPDATE_OI,
CreateEntityFlags.fDONT_INITIALIZE_ATTRIBUTES,
CreateEntityFlags.fDBHANDLER,
CreateEntityFlags.fIGNORE_PERMISSIONS );
/**
* Flags that are used to create EIs in the new OI from a cache of EIs.
*/
protected static final EnumSet INCLUDE_FLAGS = EnumSet.of( IncludeFlags.FROM_ACTIVATE );
private static final long COL_FULL_QUAL = 0x00000004;
protected enum SqlCommand
{
INSERT, SELECT, UPDATE, DELETE;
}
protected final Task task;
protected final Application application;
protected final OiRelinker entityLinker;
protected View qual;
protected Map qualMap;
protected EnumSet activateFlags;
protected AbstractOptionsConfiguration options;
/**
* When activating, this is a cast of options as ActivateOptions.
* I.e. activateOptions = (ActivateOptions) options.
*/
protected ActivateOptions activateOptions;
protected boolean closeTransaction = true;
/**
* Keeps a list of entities that are joinable for this activate.
*/
protected Map> joinableChildren = new HashMap>();
private final Map cachedStmts;
/**
* If true then the DB is generating the row keys.
*/
private boolean isDbGenerateKeys = false;
private Integer insertCount;
private Boolean isBindAllValues;
private Boolean ignoreJoins;
/**
* If true, then surround all SQL names with back-ticks.
*/
private Boolean quoteNames = false;
/**
* This is the list of instances that have been loaded. It is limited to instances
* that have children that can be loaded in one SELECT.
*
* The sub-map is keyed by the key value of the entity instance.
*/
protected Map> loadedInstances;
/**
* This hash set keeps track of view entities that have been loaded. It's used to
* determine if we've already loaded the instances of a EntityDef.
*/
protected Set loadedViewEntities;
/**
* If a entityDef is in this set it can be loaded in a single select.
*/
private Set loadInOneSelect;
private HashMap entityCacheViewMap;
/**
* If set, this is the paging options for the activate.
*/
private Pagination pagingOptions;
private boolean joinAll1To1 = false;
protected AbstractSqlHandler( Task task, AbstractOptionsConfiguration options )
{
this.task = task;
this.application = options.getApplication();
this.options = options;
entityLinker = new OiRelinker( task );
cachedStmts = new HashMap();
}
protected SqlStatement initializeCommand( SqlCommand sqlCommand, View view )
{
return initializeCommand( sqlCommand, view, null );
}
protected SqlStatement initializeCommand( SqlCommand sqlCommand, View view, EntityDef selectRoot )
{
String useQuotes = getConfigValue( "QuoteNames" );
if ( ! StringUtils.isBlank( useQuotes ) )
quoteNames = "1ty".contains( useQuotes.substring( 0, 1 ).toLowerCase() );
String value = getConfigValue( "JoinAll1to1Relationships" );
if ( ! StringUtils.isBlank( value ) && "TY1".contains( value.substring( 0, 1 ).toUpperCase() ) ) // Catches "true", "yes", "1".
joinAll1To1 = true;
return new SqlStatement( sqlCommand, view, selectRoot );
}
/* (non-Javadoc)
* @see com.quinsoft.zeidon.dbhandler.DbHandler#insertEntity(com.quinsoft.zeidon.View, com.quinsoft.zeidon.EntityInstance, java.lang.Object)
*/
@Override
public int insertEntity(View view, List extends EntityInstance> entityInstances)
{
// All the instances should have the same EntityDef.
EntityDef entityDef = entityInstances.get(0).getEntityDef();
DataRecord dataRecord = entityDef.getDataRecord();
task.dblog().debug( "Inserting entity %s, table name = %s", entityDef.getName(), dataRecord.getRecordName() );
int numInserts = entityInstances.size();
int maxInserts = numInserts;
Integer max = getInsertCount();
if ( max != null && max > 0 )
maxInserts = Math.min( max, numInserts );
int idx = 0;
while( idx < numInserts )
{
SqlStatement stmt = initializeCommand( SqlCommand.INSERT, view );
stmt.appendCmd( "INSERT INTO ", dataRecord.getRecordName(), " ( " );
addColumnList( stmt, entityInstances.get( idx ), dataRecord, 0 );
stmt.appendColumn( " )" );
stmt.appendInsertValues( " VALUES \n (" );
EntityInstance entityInstance = null;
for ( int count = 0; count < maxInserts && idx < numInserts; count++ )
{
if ( count > 0 ) // First row?
stmt.appendInsertValues( " ),\n ( " );
entityInstance = entityInstances.get( idx );
addColumnValueList( stmt, entityInstance );
idx++;
}
stmt.appendInsertValues( " )" );
executeStatement( view, entityDef, entityInstance, stmt );
}
return 0;
}
private void addColumnValueList(SqlStatement stmt, EntityInstance entityInstance)
{
boolean firstColumn = true;
for ( DataField dataField : stmt.columns.keySet() )
{
// If the DB is generating the keys then don't add generated keys to
// the list of columns.
AttributeDef attributeDef = dataField.getAttributeDef();
if ( ! addGeneratedKeyForInsert() && attributeDef.isGenKey() && entityInstance.getAttribute( attributeDef ).isNull() )
continue;
if ( firstColumn )
firstColumn = false;
else
stmt.appendInsertValues( ", " );
getAttributeValue( stmt, stmt.insertValues, dataField, entityInstance );
}
}
/**
* Copies the value of the attribute into buffer.
* @param stmt TODO
* @param domain
* @param attributeDef TODO
* @param buffer
* @param value
*/
protected abstract void getSqlValue( SqlStatement stmt,
Domain domain,
AttributeDef attributeDef,
StringBuilder buffer,
Object value );
protected abstract String getConfigValue( String key );
@Override
public void setDbGenerateKeys( boolean set )
{
isDbGenerateKeys = set;
}
protected boolean useDbGenerateKeys()
{
return isDbGenerateKeys;
}
/**
* Indicates whether we should add the generated key as part of the insert.
* By default the value is 'false' if we're using generated keys because
* the DB will infer it. This means the genkey will not appear at all
* in the INSERT statements.
*
* However some DBs (e.g. SQLite) require us to insert a NULL value for
* the key. Sqlite will then generate its own key.
*
* @return
*/
protected boolean addGeneratedKeyForInsert()
{
return ! isDbGenerateKeys;
}
/**
* For applications that equate empty strings to null we need to convert any input
* strings from "" to null.
*/
static Object convertEmptyStringValue( Object value, AttributeDef attributeDef )
{
if ( value == null )
return null;
if ( ! ( value instanceof CharSequence ) )
return value;
if ( StringUtils.isBlank( (CharSequence ) value ) &&
attributeDef.getDomain().getApplication().nullStringEqualsEmptyString() )
{
return null;
}
return value;
}
/**
* Add the attribute value to the buffer.
* @param stmt TODO
* @param buffer
* @param dataField
* @param entityInstance
*/
void getAttributeValue(SqlStatement stmt, StringBuilder buffer, DataField dataField, EntityInstance entityInstance, boolean ignoreBind )
{
AttributeDef attributeDef = dataField.getAttributeDef();
if ( ignoreBind == false && isBindAllValues() )
{
if ( stmt.commandType != SqlCommand.INSERT )
{
stmt.addBoundAttribute( buffer, dataField );
return;
}
// There can be multiple INSERT statements for a single SQL command. We need to bind the attribute
// value instead of the data field.
Object value = entityInstance.getAttribute( attributeDef ).getValue();
value = convertEmptyStringValue( value, attributeDef );
stmt.addBoundAttribute( buffer, value );
return;
}
Object value = entityInstance.getAttribute( attributeDef ).getValue();
value = convertEmptyStringValue( value, attributeDef );
getSqlValue( stmt, attributeDef.getDomain(), attributeDef, buffer, value );
}
void getAttributeValue(SqlStatement stmt, StringBuilder buffer, DataField dataField, EntityInstance entityInstance )
{
getAttributeValue( stmt, buffer, dataField, entityInstance, false );
}
protected Task getTask()
{
return task;
}
/**
* Used for setting qualification: get the attribute value and assign it using equals.
* This method is necessary to use
* @param stmt TODO
* @param buffer
* @param dataField
* @param entityInstance
*
* @return
*/
boolean getAttributeValueEquality(SqlStatement stmt, StringBuilder buffer, DataField dataField, EntityInstance entityInstance)
{
if ( ! isBindAllValues() )
{
if ( entityInstance.getAttribute( dataField.getAttributeDef() ).isNull() )
{
buffer.append( " IS null" );
return false;
}
}
buffer.append( " = " );
getAttributeValue( stmt, buffer, dataField, entityInstance );
return true;
}
private void addColumnList(SqlStatement stmt,
EntityInstance entityInstance,
DataRecord dataRecord,
long control)
{
RelRecord relRecord = dataRecord.getRelRecord();
for ( DataField dataField : dataRecord.dataFields() )
{
AttributeDef attributeDef = dataField.getAttributeDef();
if ( activateFlags != null )
{
if ( activateFlags.contains( ActivateFlags.fKEYS_ONLY ) )
{
if ( ! attributeDef.isKey() && ! attributeDef.isForeignKey() )
continue;
}
}
if ( ! attributeDef.isActivate() )
continue;
// If the attribute is an Auto Seq attribute and the relationship
// is many-to-many then the attribute is stored in the corresponding
// table. If the command type is also INSERT then the attribute is
// not to be included in this list.
if ( attributeDef.isAutoSeq() &&
relRecord != null && relRecord.getRelationshipType() == RelRecord.MANY_TO_MANY &&
stmt.commandType == SqlCommand.INSERT )
{
continue;
}
// Skip the attribute if it wasn't updated.
if ( entityInstance != null && stmt.commandType != SqlCommand.INSERT)
{
if ( ! entityInstance.getAttribute( attributeDef ).isUpdated() )
continue;
}
if ( attributeDef.isGenKey() && stmt.commandType == SqlCommand.INSERT && ! addGeneratedKeyForInsert() )
{
// If the DB is generating the keys then don't add generated keys to
// the list of columns UNLESS the key is not null. If the key is not
// null then someone must have set it on purpose and we'll use that value.
if ( entityInstance.getAttribute( attributeDef ).isNull() )
continue;
}
// TODO: Add dbOper code?
StringBuilder colName = new StringBuilder();
// Add table name.
if ( ( control & COL_FULL_QUAL ) != 0 )
{
String tableName;
if ( attributeDef.isAutoSeq() &&
relRecord != null && relRecord.getRelationshipType() == RelRecord.MANY_TO_MANY )
{
// This is the autoseq attribute. The autoseq is stored in the correspondance
// table for m-to-m relationships.
tableName = stmt.getTableName( relRecord );
}
else
{
tableName = stmt.getTableName( dataRecord );
}
colName.append( tableName ).append( "." );
}
colName.append( dataField.getName() );
stmt.addColumn( colName, dataRecord, dataField );
}
// If entityDef can be loaded all at once and this is a many-to-many relationship
// we need to add the key of the parent table in the column list so we can use it
// later to set the cursor when we're loading attributes.
EntityDef entityDef = dataRecord.getEntityDef();
if ( stmt.commandType == SqlCommand.SELECT &&
selectAllInstances( entityDef ) && relRecord.getRelationshipType().isManyToMany() )
{
RelField relField = relRecord.getParentRelField();
StringBuilder colName = new StringBuilder();
String tableName = stmt.getTableName( relRecord );
colName.append( tableName ).append( "." );
colName.append( relField.getFieldName() );
if ( stmt.columns.size() > 0 )
stmt.appendCmd( ", " );
stmt.addColumn( colName, dataRecord, relField.getSrcDataField() );
}
}
/**
* Parses the qualification object and puts the information in the qualMap.
* The
* @param view
*/
private void loadQualificationObject( LodDef lodDef )
{
qualMap = new HashMap();
if ( qual == null )
return;
for ( EntityInstance entitySpec : qual.cursor( "EntitySpec" ).eachEntity() )
{
if ( entitySpec.getAttribute( "EntityName" ).isNull() )
throw new ZeidonException("Qualification view is missing entity name in EntitySpec" );
String entityName = entitySpec.getAttribute( "EntityName" ).getString();
if ( StringUtils.isBlank( entityName ) )
throw new ZeidonException("Qualification view is missing entity name in EntitySpec" );
EntityDef entityDef = lodDef.getEntityDef( entityName );
QualEntity qualEntity = new QualEntity( entitySpec, entityDef );
qualMap.put( entityDef, qualEntity );
String openSql = entitySpec.getAttribute( "OpenSQL" ).getString();
if ( ! StringUtils.isBlank( openSql ) )
{
qualEntity.openSql = openSql;
qualEntity.setOpenSqlAttributeList( entitySpec.getAttribute( "OpenSQL_AttributeList" ).getString() );
continue;
}
int parenCount = 0;
EntityDef qualAttribDef = entitySpec.getEntityDef().getLodDef().getEntityDef( "QualAttrib" );
for ( EntityInstance qualAttribInstance : entitySpec.getChildren( qualAttribDef, true ) )
{
//
// Verify Oper
//
if ( qualAttribInstance.getAttribute( "Oper" ).isNull() )
throw new ZeidonException( "QualAttrib for " + entityName + " is missing Oper" );
QualAttrib qualAttrib = new QualAttrib( qualAttribInstance.getAttribute( "Oper" ).getString() );
if ( qualAttrib.oper.equals( "EXCLUDE" ) )
{
qualEntity.exclude = true;
continue;
}
if ( qualEntity.exclude )
throw new ZeidonException( "Entity '%s' has EXCLUDE but has additional qualification",
qualEntity.entityDef.getName() );
parenCount += CharSetUtils.count( qualAttrib.oper, "(" );
parenCount -= CharSetUtils.count( qualAttrib.oper, ")" );
//
// Verify EntityName
//
if ( ! qualAttribInstance.getAttribute( "EntityName" ).isNull() )
{
String qualEntityName = qualAttribInstance.getAttribute( "EntityName" ).getString();
qualAttrib.entityDef = lodDef.getEntityDef( qualEntityName );
if ( qualAttrib.entityDef.isDerived() || qualAttrib.entityDef.isDerivedPath() ||
qualAttrib.entityDef.getDataRecord() == null )
{
throw new ZeidonException( "Entity " + entityName + " is derived or doesn't have DB information.");
}
}
if ( qualAttrib.oper.equals( "EXISTS" ) || qualAttrib.oper.equals( "NOT EXISTS" ) )
{
if ( qualAttrib.entityDef == null )
throw new ZeidonException("Oper 'EXISTS'/'NOT EXISTS' requires an entity specification");
// Change the oper to =/!=
if ( qualAttrib.oper.equals( "EXISTS" ) )
qualAttrib.oper = "!=";
else
{
qualAttrib.oper = "=";
qualEntity.hasDoesNotExist = true;
}
qualAttrib.attributeDef = qualAttrib.entityDef.getKeys().get( 0 );
}
//
// Verify AttribName
//
if ( ! qualAttribInstance.getAttribute( "AttributeName" ).isNull() || qualAttrib.attributeDef != null )
{
if ( qualAttrib.attributeDef == null )
{
String attribName = qualAttribInstance.getAttribute( "AttributeName" ).getString();
if ( qualAttrib.entityDef == null )
throw new ZeidonException( "QualAttrib has attribute defined but no valid entity" );
qualAttrib.attributeDef = qualAttrib.entityDef.getAttribute( attribName );
}
// In some cases, we might be qualifying an entity using an attribute
// from a child entity. If the child attribute is a key AND that key
// is the source attribute for a many-to-one relationship then the
// attribute's value is also stored in the parent entity (the entity
// we are qualifying) as a foreign key. It will be much quicker to
// perform qualification on just the foreign key, so change the
// qualification to reference the foreign key.
DataRecord dataRecord = qualAttrib.entityDef.getDataRecord();
RelRecord relRecord = dataRecord.getRelRecord();
while ( qualAttrib.attributeDef.isKey() &&
qualAttrib.entityDef != qualEntity.entityDef &&
relRecord != null &&
relRecord.getRelationshipType() == RelRecord.CHILD_IS_SOURCE )
{
assert relRecord.getRelFields().size() == 1;
// Find the rel field for the qualifying attribute.
RelField relField = relRecord.getRelFields().get( 0 );
// Change the column we are qualifying on.
DataField dataField = relField.getRelDataField();
qualAttrib.attributeDef = dataField.getAttributeDef();
qualAttrib.entityDef = qualAttrib.attributeDef.getEntityDef();
dataRecord = qualAttrib.entityDef.getDataRecord();
relRecord = dataRecord.getRelRecord();
}
}
if ( qualAttrib.oper.equals( "ORDERBY" ) )
{
if ( qualAttrib.attributeDef == null )
throw new ZeidonException("Using Order By in qualification requires an attribute name" );
boolean descending = false;
if ( ! qualAttribInstance.getAttribute( "Value" ).isNull() ) {
String str = qualAttribInstance.getAttribute( "Value" ).getString().toLowerCase();
descending = str.startsWith( "desc" );
}
qualEntity.addOrderBy( qualAttrib.attributeDef, descending );
continue;
}
//
// Verify Value
//
if ( ! qualAttribInstance.getAttribute( "Value" ).isNull() )
{
if ( qualAttrib.attributeDef == null )
throw new ZeidonException("QualAttrib with value requires Entity.Attrib");
String value = qualAttribInstance.getAttribute( "Value" ).getString();
if ( qualAttrib.oper.equals( "LIKE" ) )
{
// If oper is "LIKE" then a qualification value that is invalid for the domain
// is possible. E.g. "(617)%' is valid qualification for a phone number. We'll
// assume the user knows what she's doing so we'll skip domain processing.
qualAttrib.value = value;
}
else
{
// Get the string value from the qualification object, convert it to the
// domain's internal value, and then to an a string representation of the
// internal value.
Domain domain = qualAttrib.attributeDef.getDomain();
qualAttrib.value = domain.convertExternalValue( task, null, qualAttrib.attributeDef, null, value );
}
}
//
// Verify KeyList
//
for ( EntityInstance kl : qualAttribInstance.getChildren( "KeyList" ) )
{
if ( qualAttrib.valueList == null )
qualAttrib.valueList = new ArrayList<>();
String value = null;
if ( ! kl.getAttribute( "StringValue" ).isNull() )
value = kl.getAttribute( "StringValue" ).getString();
else
if ( ! kl.getAttribute( "IntegerValue" ).isNull() )
value = kl.getAttribute( "IntegerValue" ).getString();
else
throw new ZeidonException( "KeyList entity doesn't have IntegerValue or StringValue" );
Domain domain = qualAttrib.attributeDef.getDomain();
Object objectValue = domain.convertExternalValue( task, null, qualAttrib.attributeDef, null, value );
qualAttrib.valueList.add( objectValue );
}
if ( qualAttrib.valueList != null )
{
if ( qualAttrib.value != null )
throw new ZeidonException( "KeyList is specified for a QualAttrib that also has a value attribute" );
}
//
// Verify SourceViewName
//
if ( ! qualAttribInstance.getAttribute( "SourceViewName" ).isNull() )
throw new ZeidonException( "SourceViewName is currently unsupported for Activate Qualification" );
if ( ! qualAttribInstance.getAttribute( "SourceViewID" ).isNull() )
throw new ZeidonException( "SourceViewID is not supported by Java Object Engine" );
//
// Verify SourceEntityName
//
if ( ! qualAttribInstance.getAttribute( "SourceEntityName" ).isNull() )
{
if ( qualAttribInstance.getAttribute( "SourceAttributeName" ).isNull() )
throw new ZeidonException( "SourceEntityName is specified but SourceAttributeName is not." );
loadQualAttributeColumn( lodDef, qualAttribInstance, qualAttrib );
}
// =================================================================
// ===
// === Validate Qualification attributes.
// ===
// =================================================================
// TODO: Add the rest of the checks in fnSqlRetrieveQualAttrib.
// Check to see if we should add qual for both "" and NULL.
if ( addCheckForNullAndEmptyString( qualEntity, qualAttrib ) )
qualEntity.addQualAttrib( qualAttrib ); // No; just add qualAttrib as normal.
} // for each QualAttrib...
assert parenCount == 0;
} // for each EntitySpec...
// For pagination we *must* have the keys as part of the ordering, even if
// it's the last value to order by. This is so sorting will always have
// the same results.
if ( pagingOptions != null )
{
EntityDef root = lodDef.getRoot();
QualEntity qualRootEntity = qualMap.get( root );
if ( qualRootEntity == null )
{
qualRootEntity = new QualEntity( null, root );
qualMap.put( root, qualRootEntity );
}
if ( qualRootEntity.checkForKeysInOrderBy() ) // Add the keys if they aren't there.
getTask().log().trace( "Key(s) added to ordering for pagination" );
qualRootEntity.activateLimit = pagingOptions.getPageSize();
// Copy the root ordering back to the ActivateOptions so we can use
// them later when attempting to load the next page.
activateOptions.setRootActivateOrdering( qualRootEntity.ordering );
}
} // method loadQualificationObject
/**
* Take a single qualAttrib that looks for ""/null string and add two qualAttribs
* to the qualEntity to explicitly look for "" and null string.
*
* return: True if qual attrib needs to be added (i.e. it wasn't added here).
*/
private boolean addCheckForNullAndEmptyString( QualEntity qualEntity, QualAttrib qualAttrib )
{
if ( ! qualAttrib.isNullAndEmptyString() )
return true;
qualEntity.addQualAttrib( new QualAttrib( "(" ) );
qualAttrib.value = null;
qualEntity.addQualAttrib( qualAttrib );
if ( qualAttrib.operIsInequality() )
qualEntity.addQualAttrib( new QualAttrib( "AND" ) );
else
qualEntity.addQualAttrib( new QualAttrib( "OR" ) );
qualAttrib = new QualAttrib( qualAttrib ); // Clone so we can change the value.
qualAttrib.value = "";
qualEntity.addQualAttrib( qualAttrib );
qualEntity.addQualAttrib( new QualAttrib( ")" ) );
return false;
}
/**
* The value in the qualification is a column name, not a text value. This means that the
* qualification is comparing the column to another column. Load the values in qualAttrib.
*
* @param lodDef
* @param qualAttribInstance
* @param qualAttrib
*/
private void loadQualAttributeColumn( LodDef lodDef, EntityInstance qualAttribInstance, QualAttrib qualAttrib )
{
String srcEntityName = qualAttribInstance.getAttribute( "SourceEntityName" ).getString();
String srcAttributeName = qualAttribInstance.getAttribute( "SourceAttributeName" ).getString();
EntityDef entityDef = lodDef.getEntityDef( srcEntityName, false );
if ( entityDef == null )
throw new ZeidonException( "EntityName specified in qualification is unknown: %s", srcEntityName );
AttributeDef attributeDef = entityDef.getAttribute( srcAttributeName, false );
if ( attributeDef == null )
throw new ZeidonException( "AttributeName specified in qualification is unknown: %s", srcAttributeName );
qualAttrib.columnAttributeValue = attributeDef;
// Verify that columnAttributeValue.getViewEntity is a child of qualAttrib.viewEntity or
// vice-versa. We could potentially support siblings if they have 1-to-1 relationships
// with their parents.
if ( entityDef != qualAttrib.entityDef &&
! entityDef.isAncestorOf( qualAttrib.entityDef ) && ! qualAttrib.entityDef.isAncestorOf( entityDef ) )
{
throw new ZeidonException( "When qualifying an attribute with another attribute in the same query, " +
"one attribute must be a descendant of the other (i.e. they may not be " +
"siblings." )
.appendMessage( "Attribute 1 = %s.%s", qualAttrib.entityDef.getName(),
qualAttrib.attributeDef.getName() )
.appendMessage( "Attribute 2 = %s.%s", entityDef.getName(),
attributeDef.getName() );
}
}
@Override
public int beginActivate( View view, View qual, EnumSet control )
{
this.qual = qual;
this.activateFlags = control;
activateOptions = (ActivateOptions) options;
pagingOptions = activateOptions.getPagingOptions();
loadQualificationObject( view.getLodDef() );
loadedViewEntities = new HashSet<>();
determineEntitiesThatCanBeLoadedInOneSelect( view );
if ( activateOptions.isSingleTransaction() )
closeTransaction = false;
return 0;
}
/**
* This determines what ViewEntities can be loaded in a single SELECT statement.
* @param view
*/
private void determineEntitiesThatCanBeLoadedInOneSelect(View view)
{
// If an entity can be loaded in one SELECT we need to keep a map of its parent
// EIs so we can set cursors appropriately.
loadedInstances = new HashMap>();
loadInOneSelect = new HashSet<>();
for ( EntityDef entityDef : view.getLodDef().getEntityDefs() )
{
if ( entityCanBeLoadedWithSingleSelect( entityDef ) )
{
loadInOneSelect.add( entityDef );
EntityDef parent = entityDef.getParent();
if ( parent != null && ! loadedInstances.containsKey( parent ) )
loadedInstances.put( parent, new HashMap