Open Source Repository

Home /hibernate/hibernate-3.2.6.ga | Repository Home



org/hibernate/persister/entity/SingleTableEntityPersister.java
//$Id: SingleTableEntityPersister.java 10040 2006-06-22 19:51:43Z [email protected] $
package org.hibernate.persister.entity;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;

import org.hibernate.EntityMode;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.cache.CacheConcurrencyStrategy;
import org.hibernate.engine.Mapping;
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.engine.ExecuteUpdateResultCheckStyle;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.Formula;
import org.hibernate.mapping.Join;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.Selectable;
import org.hibernate.mapping.Subclass;
import org.hibernate.mapping.Table;
import org.hibernate.mapping.Value;
import org.hibernate.sql.InFragment;
import org.hibernate.sql.Insert;
import org.hibernate.sql.SelectFragment;
import org.hibernate.type.AssociationType;
import org.hibernate.type.DiscriminatorType;
import org.hibernate.type.Type;
import org.hibernate.util.ArrayHelper;
import org.hibernate.util.MarkerObject;

/**
 * The default implementation of the <tt>EntityPersister</tt> interface.
 * Implements the "table-per-class-hierarchy" or "roll-up" mapping strategy
 * for an entity class and its inheritence hierarchy.  This is implemented
 * as a single table holding all classes in the hierarchy with a discrimator
 * column used to determine which concrete class is referenced.
 *
 @author Gavin King
 */
public class SingleTableEntityPersister extends AbstractEntityPersister {

  // the class hierarchy structure
  private final int joinSpan;
  private final String[] qualifiedTableNames;
  private final boolean[] isInverseTable;
  private final boolean[] isNullableTable;
  private final String[][] keyColumnNames;
  private final boolean[] cascadeDeleteEnabled;
  private final boolean hasSequentialSelects;
  
  private final String[] spaces;

  private final String[] subclassClosure;

  private final String[] subclassTableNameClosure;
  private final boolean[] subclassTableIsLazyClosure;
  private final boolean[] isInverseSubclassTable;
  private final boolean[] isNullableSubclassTable;
  private final boolean[] subclassTableSequentialSelect;
  private final String[][] subclassTableKeyColumnClosure;
  private final boolean[] isClassOrSuperclassTable;

  // properties of this class, including inherited properties
  private final int[] propertyTableNumbers;

  // the closure of all columns used by the entire hierarchy including
  // subclasses and superclasses of this class
  private final int[] subclassPropertyTableNumberClosure;

  private final int[] subclassColumnTableNumberClosure;
  private final int[] subclassFormulaTableNumberClosure;

  // discriminator column
  private final Map subclassesByDiscriminatorValue = new HashMap();
  private final boolean forceDiscriminator;
  private final String discriminatorColumnName;
  private final String discriminatorFormula;
  private final String discriminatorFormulaTemplate;
  private final String discriminatorAlias;
  private final Type discriminatorType;
  private final String discriminatorSQLValue;
  private final boolean discriminatorInsertable;

  private final String[] constraintOrderedTableNames;
  private final String[][] constraintOrderedKeyColumnNames;

  //private final Map propertyTableNumbersByName = new HashMap();
  private final Map propertyTableNumbersByNameAndSubclass = new HashMap();
  
  private final Map sequentialSelectStringsByEntityName = new HashMap();

  private static final Object NULL_DISCRIMINATOR = new MarkerObject("<null discriminator>");
  private static final Object NOT_NULL_DISCRIMINATOR = new MarkerObject("<not null discriminator>");

  //INITIALIZATION:

  public SingleTableEntityPersister(
      final PersistentClass persistentClass, 
      final CacheConcurrencyStrategy cache,
      final SessionFactoryImplementor factory,
      final Mapping mapping)
  throws HibernateException {

    super(persistentClass, cache, factory);

    // CLASS + TABLE

    joinSpan = persistentClass.getJoinClosureSpan()+1;
    qualifiedTableNames = new String[joinSpan];
    isInverseTable = new boolean[joinSpan];
    isNullableTable = new boolean[joinSpan];
    keyColumnNames = new String[joinSpan][];
    final Table table = persistentClass.getRootTable();
    qualifiedTableNames[0= table.getQualifiedName
        factory.getDialect()
        factory.getSettings().getDefaultCatalogName()
        factory.getSettings().getDefaultSchemaName() 
    );
    isInverseTable[0false;
    isNullableTable[0false;
    keyColumnNames[0= getIdentifierColumnNames();
    cascadeDeleteEnabled = new boolean[joinSpan];

    // Custom sql
    customSQLInsert = new String[joinSpan];
    customSQLUpdate = new String[joinSpan];
    customSQLDelete = new String[joinSpan];
    insertCallable = new boolean[joinSpan];
    updateCallable = new boolean[joinSpan];
    deleteCallable = new boolean[joinSpan];
    insertResultCheckStyles = new ExecuteUpdateResultCheckStyle[joinSpan];
    updateResultCheckStyles = new ExecuteUpdateResultCheckStyle[joinSpan];
    deleteResultCheckStyles = new ExecuteUpdateResultCheckStyle[joinSpan];

    customSQLInsert[0= persistentClass.getCustomSQLInsert();
    insertCallable[0= customSQLInsert[0!= null && persistentClass.isCustomInsertCallable();
    insertResultCheckStyles[0= persistentClass.getCustomSQLInsertCheckStyle() == null
                    ? ExecuteUpdateResultCheckStyle.determineDefaultcustomSQLInsert[0], insertCallable[0] )
                    : persistentClass.getCustomSQLInsertCheckStyle();
    customSQLUpdate[0= persistentClass.getCustomSQLUpdate();
    updateCallable[0= customSQLUpdate[0!= null && persistentClass.isCustomUpdateCallable();
    updateResultCheckStyles[0= persistentClass.getCustomSQLUpdateCheckStyle() == null
                    ? ExecuteUpdateResultCheckStyle.determineDefaultcustomSQLUpdate[0], updateCallable[0] )
                    : persistentClass.getCustomSQLUpdateCheckStyle();
    customSQLDelete[0= persistentClass.getCustomSQLDelete();
    deleteCallable[0= customSQLDelete[0!= null && persistentClass.isCustomDeleteCallable();
    deleteResultCheckStyles[0= persistentClass.getCustomSQLDeleteCheckStyle() == null
                    ? ExecuteUpdateResultCheckStyle.determineDefaultcustomSQLDelete[0], deleteCallable[0] )
                    : persistentClass.getCustomSQLDeleteCheckStyle();

    // JOINS

    Iterator joinIter = persistentClass.getJoinClosureIterator();
    int j = 1;
    while joinIter.hasNext() ) {
      Join join = (JoinjoinIter.next();
      qualifiedTableNames[j= join.getTable().getQualifiedName
          factory.getDialect()
          factory.getSettings().getDefaultCatalogName()
          factory.getSettings().getDefaultSchemaName() 
      );
      isInverseTable[j= join.isInverse();
      isNullableTable[j= join.isOptional();
      cascadeDeleteEnabled[j= join.getKey().isCascadeDeleteEnabled() && 
        factory.getDialect().supportsCascadeDelete();

      customSQLInsert[j= join.getCustomSQLInsert();
      insertCallable[j= customSQLInsert[j!= null && join.isCustomInsertCallable();
      insertResultCheckStyles[j= join.getCustomSQLInsertCheckStyle() == null
                                    ? ExecuteUpdateResultCheckStyle.determineDefaultcustomSQLInsert[j], insertCallable[j] )
                                      : join.getCustomSQLInsertCheckStyle();
      customSQLUpdate[j= join.getCustomSQLUpdate();
      updateCallable[j= customSQLUpdate[j!= null && join.isCustomUpdateCallable();
      updateResultCheckStyles[j= join.getCustomSQLUpdateCheckStyle() == null
                                    ? ExecuteUpdateResultCheckStyle.determineDefaultcustomSQLUpdate[j], updateCallable[j] )
                                      : join.getCustomSQLUpdateCheckStyle();
      customSQLDelete[j= join.getCustomSQLDelete();
      deleteCallable[j= customSQLDelete[j!= null && join.isCustomDeleteCallable();
      deleteResultCheckStyles[j= join.getCustomSQLDeleteCheckStyle() == null
                                    ? ExecuteUpdateResultCheckStyle.determineDefaultcustomSQLDelete[j], deleteCallable[j] )
                                      : join.getCustomSQLDeleteCheckStyle();

      Iterator iter = join.getKey().getColumnIterator();
      keyColumnNames[jnew Stringjoin.getKey().getColumnSpan() ];
      int i = 0;
      while iter.hasNext() ) {
        Column col = (Columniter.next();
        keyColumnNames[j][i++= col.getQuotedNamefactory.getDialect() );
      }

      j++;
    }

    constraintOrderedTableNames = new String[qualifiedTableNames.length];
    constraintOrderedKeyColumnNames = new String[qualifiedTableNames.length][];
    for int i = qualifiedTableNames.length - 1, position = 0; i >= 0; i--, position++ ) {
      constraintOrderedTableNames[position= qualifiedTableNames[i];
      constraintOrderedKeyColumnNames[position= keyColumnNames[i];
    }

    spaces = ArrayHelper.join(
        qualifiedTableNames, 
        ArrayHelper.toStringArraypersistentClass.getSynchronizedTables() )
    );
    
    final boolean lazyAvailable = isInstrumented(EntityMode.POJO);

    boolean hasDeferred = false;
    ArrayList subclassTables = new ArrayList();
    ArrayList joinKeyColumns = new ArrayList();
    ArrayList isConcretes = new ArrayList();
    ArrayList isDeferreds = new ArrayList();
    ArrayList isInverses = new ArrayList();
    ArrayList isNullables = new ArrayList();
    ArrayList isLazies = new ArrayList();
    subclassTables.addqualifiedTableNames[0] );
    joinKeyColumns.addgetIdentifierColumnNames() );
    isConcretes.add(Boolean.TRUE);
    isDeferreds.add(Boolean.FALSE);
    isInverses.add(Boolean.FALSE);
    isNullables.add(Boolean.FALSE);
    isLazies.add(Boolean.FALSE);
    joinIter = persistentClass.getSubclassJoinClosureIterator();
    while joinIter.hasNext() ) {
      Join join = (JoinjoinIter.next();
      isConcretes.addnew BooleanpersistentClass.isClassOrSuperclassJoin(join) ) );
      isDeferreds.addnew Booleanjoin.isSequentialSelect() ) );
      isInverses.addnew Booleanjoin.isInverse() ) );
      isNullables.addnew Booleanjoin.isOptional() ) );
      isLazies.addnew BooleanlazyAvailable && join.isLazy() ) );
      if join.isSequentialSelect() && !persistentClass.isClassOrSuperclassJoin(join) ) hasDeferred = true;
      subclassTables.addjoin.getTable().getQualifiedName
          factory.getDialect()
          factory.getSettings().getDefaultCatalogName()
          factory.getSettings().getDefaultSchemaName() 
      ) );
      Iterator iter = join.getKey().getColumnIterator();
      String[] keyCols = new Stringjoin.getKey().getColumnSpan() ];
      int i = 0;
      while iter.hasNext() ) {
        Column col = (Columniter.next();
        keyCols[i++= col.getQuotedNamefactory.getDialect() );
      }
      joinKeyColumns.add(keyCols);
    }
    
    subclassTableSequentialSelect = ArrayHelper.toBooleanArray(isDeferreds);
    subclassTableNameClosure = ArrayHelper.toStringArray(subclassTables);
    subclassTableIsLazyClosure = ArrayHelper.toBooleanArray(isLazies);
    subclassTableKeyColumnClosure = ArrayHelper.to2DStringArray(joinKeyColumns);
    isClassOrSuperclassTable = ArrayHelper.toBooleanArray(isConcretes);
    isInverseSubclassTable = ArrayHelper.toBooleanArray(isInverses);
    isNullableSubclassTable = ArrayHelper.toBooleanArray(isNullables);
    hasSequentialSelects = hasDeferred;

    // DISCRIMINATOR

    final Object discriminatorValue;
    if persistentClass.isPolymorphic() ) {
      Value discrimValue = persistentClass.getDiscriminator();
      if (discrimValue==null) {
        throw new MappingException("discriminator mapping required for single table polymorphic persistence");
      }
      forceDiscriminator = persistentClass.isForceDiscriminator();
      Selectable selectable = (SelectablediscrimValue.getColumnIterator().next();
      if discrimValue.hasFormula() ) {
        Formula formula = (Formulaselectable;
        discriminatorFormula = formula.getFormula();
        discriminatorFormulaTemplate = formula.getTemplatefactory.getDialect(), factory.getSqlFunctionRegistry() );
        discriminatorColumnName = null;
        discriminatorAlias = "clazz_";
      }
      else {
        Column column = (Columnselectable;
        discriminatorColumnName = column.getQuotedNamefactory.getDialect() );
        discriminatorAlias = column.getAliasfactory.getDialect(), persistentClass.getRootTable() );
        discriminatorFormula = null;
        discriminatorFormulaTemplate = null;
      }
      discriminatorType = persistentClass.getDiscriminator().getType();
      if persistentClass.isDiscriminatorValueNull() ) {
        discriminatorValue = NULL_DISCRIMINATOR;
        discriminatorSQLValue = InFragment.NULL;
        discriminatorInsertable = false;
      }
      else if persistentClass.isDiscriminatorValueNotNull() ) {
        discriminatorValue = NOT_NULL_DISCRIMINATOR;
        discriminatorSQLValue = InFragment.NOT_NULL;
        discriminatorInsertable = false;
      }
      else {
        discriminatorInsertable = persistentClass.isDiscriminatorInsertable() && !discrimValue.hasFormula();
        try {
          DiscriminatorType dtype = (DiscriminatorTypediscriminatorType;
          discriminatorValue = dtype.stringToObjectpersistentClass.getDiscriminatorValue() );
          discriminatorSQLValue = dtype.objectToSQLStringdiscriminatorValue, factory.getDialect() );
        }
        catch (ClassCastException cce) {
          throw new MappingException("Illegal discriminator type: " + discriminatorType.getName() );
        }
        catch (Exception e) {
          throw new MappingException("Could not format discriminator value to SQL string", e);
        }
      }
    }
    else {
      forceDiscriminator = false;
      discriminatorInsertable = false;
      discriminatorColumnName = null;
      discriminatorAlias = null;
      discriminatorType = null;
      discriminatorValue = null;
      discriminatorSQLValue = null;
      discriminatorFormula = null;
      discriminatorFormulaTemplate = null;
    }

    // PROPERTIES

    propertyTableNumbers = new intgetPropertySpan() ];
    Iterator iter = persistentClass.getPropertyClosureIterator();
    int i=0;
    whileiter.hasNext() ) {
      Property prop = (Propertyiter.next();
      propertyTableNumbers[i++= persistentClass.getJoinNumber(prop);

    }

    //TODO: code duplication with JoinedSubclassEntityPersister
    
    ArrayList columnJoinNumbers = new ArrayList();
    ArrayList formulaJoinedNumbers = new ArrayList();
    ArrayList propertyJoinNumbers = new ArrayList();
    
    iter = persistentClass.getSubclassPropertyClosureIterator();
    while iter.hasNext() ) {
      Property prop = (Propertyiter.next();
      Integer join = new IntegerpersistentClass.getJoinNumber(prop) );
      propertyJoinNumbers.add(join);

      //propertyTableNumbersByName.put( prop.getName(), join );
      propertyTableNumbersByNameAndSubclass.put
          prop.getPersistentClass().getEntityName() '.' + prop.getName()
          join 
      );

      Iterator citer = prop.getColumnIterator();
      while citer.hasNext() ) {
        Selectable thing = (Selectableciter.next();
        if thing.isFormula() ) {
          formulaJoinedNumbers.add(join);
        }
        else {
          columnJoinNumbers.add(join);
        }
      }
    }
    subclassColumnTableNumberClosure = ArrayHelper.toIntArray(columnJoinNumbers);
    subclassFormulaTableNumberClosure = ArrayHelper.toIntArray(formulaJoinedNumbers);
    subclassPropertyTableNumberClosure = ArrayHelper.toIntArray(propertyJoinNumbers);

    int subclassSpan = persistentClass.getSubclassSpan() 1;
    subclassClosure = new String[subclassSpan];
    subclassClosure[0= getEntityName();
    if persistentClass.isPolymorphic() ) {
      subclassesByDiscriminatorValue.putdiscriminatorValue, getEntityName() );
    }

    // SUBCLASSES
    if persistentClass.isPolymorphic() ) {
      iter = persistentClass.getSubclassIterator();
      int k=1;
      while iter.hasNext() ) {
        Subclass sc = (Subclassiter.next();
        subclassClosure[k++= sc.getEntityName();
        if sc.isDiscriminatorValueNull() ) {
          subclassesByDiscriminatorValue.putNULL_DISCRIMINATOR, sc.getEntityName() );
        }
        else if sc.isDiscriminatorValueNotNull() ) {
          subclassesByDiscriminatorValue.putNOT_NULL_DISCRIMINATOR, sc.getEntityName() );
        }
        else {
          try {
            DiscriminatorType dtype = (DiscriminatorTypediscriminatorType;
            subclassesByDiscriminatorValue.put(
              dtype.stringToObjectsc.getDiscriminatorValue() ),
              sc.getEntityName()
            );
          }
          catch (ClassCastException cce) {
            throw new MappingException("Illegal discriminator type: " + discriminatorType.getName() );
          }
          catch (Exception e) {
            throw new MappingException("Error parsing discriminator value", e);
          }
        }
      }
    }

    initLockers();

    initSubclassPropertyAliasesMap(persistentClass);
    
    postConstruct(mapping);

  }

  protected boolean isInverseTable(int j) {
    return isInverseTable[j];
  }

  protected boolean isInverseSubclassTable(int j) {
    return isInverseSubclassTable[j];
  }

  public String getDiscriminatorColumnName() {
    return discriminatorColumnName;
  }

  protected String getDiscriminatorAlias() {
    return discriminatorAlias;
  }

  protected String getDiscriminatorFormulaTemplate() {
    return discriminatorFormulaTemplate;
  }

  public String getTableName() {
    return qualifiedTableNames[0];
  }

  public Type getDiscriminatorType() {
    return discriminatorType;
  }

  public String getDiscriminatorSQLValue() {
    return discriminatorSQLValue;
  }

  public String[] getSubclassClosure() {
    return subclassClosure;
  }

  public String getSubclassForDiscriminatorValue(Object value) {
    if (value==null) {
      return (StringsubclassesByDiscriminatorValue.get(NULL_DISCRIMINATOR);
    }
    else {
      String result = (StringsubclassesByDiscriminatorValue.get(value);
      if (result==nullresult = (StringsubclassesByDiscriminatorValue.get(NOT_NULL_DISCRIMINATOR);
      return result;
    }
  }

  public Serializable[] getPropertySpaces() {
    return spaces;
  }

  //Access cached SQL

  protected boolean isDiscriminatorFormula() {
    return discriminatorColumnName==null;
  }

  protected String getDiscriminatorFormula() {
    return discriminatorFormula;
  }

  protected String getTableName(int j) {
    return qualifiedTableNames[j];
  }
  
  protected String[] getKeyColumns(int j) {
    return keyColumnNames[j];
  }
  
  protected boolean isTableCascadeDeleteEnabled(int j) {
    return cascadeDeleteEnabled[j];
  }
  
  protected boolean isPropertyOfTable(int property, int j) {
    return propertyTableNumbers[property]==j;
  }

  protected boolean isSubclassTableSequentialSelect(int j) {
    return subclassTableSequentialSelect[j&& !isClassOrSuperclassTable[j];
  }
  
  // Execute the SQL:

  public String fromTableFragment(String name) {
    return getTableName() ' ' + name;
  }

  public String filterFragment(String aliasthrows MappingException {
    String result = discriminatorFilterFragment(alias);
    if hasWhere() ) result += " and " + getSQLWhereString(alias);
    return result;
  }
  
  public String oneToManyFilterFragment(String aliasthrows MappingException {
    return forceDiscriminator ?
      discriminatorFilterFragment(alias:
      "";
  }

  private String discriminatorFilterFragment(String aliasthrows MappingException {
    if needsDiscriminator() ) {
      InFragment frag = new InFragment();

      if isDiscriminatorFormula() ) {
        frag.setFormulaalias, getDiscriminatorFormulaTemplate() );
      }
      else {
        frag.setColumnalias, getDiscriminatorColumnName() );
      }

      String[] subclasses = getSubclassClosure();
      for int i=0; i<subclasses.length; i++ ) {
        final Queryable queryable = (QueryablegetFactory().getEntityPersistersubclasses[i] );
        if !queryable.isAbstract() ) frag.addValuequeryable.getDiscriminatorSQLValue() );
      }

      StringBuffer buf = new StringBuffer(50)
        .append(" and ")
        .appendfrag.toFragmentString() );

      return buf.toString();
    }
    else {
      return "";
    }
  }

  private boolean needsDiscriminator() {
    return forceDiscriminator || isInherited();
  }

  public String getSubclassPropertyTableName(int i) {
    return subclassTableNameClosuresubclassPropertyTableNumberClosure[i] ];
  }

  protected void addDiscriminatorToSelect(SelectFragment select, String name, String suffix) {
    if isDiscriminatorFormula() ) {
      select.addFormulaname, getDiscriminatorFormulaTemplate(), getDiscriminatorAlias() );
    }
    else {
      select.addColumnname, getDiscriminatorColumnName(),  getDiscriminatorAlias() );
    }
  }
  
  protected int[] getPropertyTableNumbersInSelect() {
    return propertyTableNumbers;
  }

  protected int getSubclassPropertyTableNumber(int i) {
    return subclassPropertyTableNumberClosure[i];
  }

  public int getTableSpan() {
    return joinSpan;
  }

  protected void addDiscriminatorToInsert(Insert insert) {

    if (discriminatorInsertable) {
      insert.addColumngetDiscriminatorColumnName(), discriminatorSQLValue );
    }

  }

  protected int[] getSubclassColumnTableNumberClosure() {
    return subclassColumnTableNumberClosure;
  }

  protected int[] getSubclassFormulaTableNumberClosure() {
    return subclassFormulaTableNumberClosure;
  }

  protected int[] getPropertyTableNumbers() {
    return propertyTableNumbers;
  }
    
  protected boolean isSubclassPropertyDeferred(String propertyName, String entityName) {
    return hasSequentialSelects && 
      isSubclassTableSequentialSelectgetSubclassPropertyTableNumber(propertyName, entityName) );
  }
  
  public boolean hasSequentialSelect() {
    return hasSequentialSelects;
  }
  
  private int getSubclassPropertyTableNumber(String propertyName, String entityName) {
    Type type = propertyMapping.toType(propertyName);
    if type.isAssociationType() && ( (AssociationTypetype ).useLHSPrimaryKey() ) return 0;
    final Integer tabnum = (IntegerpropertyTableNumbersByNameAndSubclass.get(entityName + '.' + propertyName);
    return tabnum==null : tabnum.intValue();
  }
  
  protected String getSequentialSelect(String entityName) {
    return (StringsequentialSelectStringsByEntityName.get(entityName);
  }

  private String generateSequentialSelect(Loadable persister) {
    //if ( this==persister || !hasSequentialSelects ) return null;

    //note that this method could easily be moved up to BasicEntityPersister,
    //if we ever needed to reuse it from other subclasses
    
    //figure out which tables need to be fetched
    AbstractEntityPersister subclassPersister = (AbstractEntityPersisterpersister;
    HashSet tableNumbers = new HashSet();
    String[] props = subclassPersister.getPropertyNames();
    String[] classes = subclassPersister.getPropertySubclassNames();
    for int i=0; i<props.length; i++ ) {
      int propTableNumber = getSubclassPropertyTableNumberprops[i], classes[i] );
      if isSubclassTableSequentialSelect(propTableNumber&& !isSubclassTableLazy(propTableNumber) ) {
        tableNumbers.addnew Integer(propTableNumber) );
      }
    }
    if tableNumbers.isEmpty() ) return null;
    
    //figure out which columns are needed
    ArrayList columnNumbers = new ArrayList();
    final int[] columnTableNumbers = getSubclassColumnTableNumberClosure();
    for int i=0; i<getSubclassColumnClosure().length; i++ ) {
      if tableNumbers.containsnew IntegercolumnTableNumbers[i] ) ) ) {
        columnNumbers.addnew Integer(i) );
      }
    }
    
    //figure out which formulas are needed
    ArrayList formulaNumbers = new ArrayList();
    final int[] formulaTableNumbers = getSubclassColumnTableNumberClosure();
    for int i=0; i<getSubclassFormulaTemplateClosure().length; i++ ) {
      if tableNumbers.containsnew IntegerformulaTableNumbers[i] ) ) ) {
        formulaNumbers.addnew Integer(i) );
      }
    }
    
    //render the SQL
    return renderSelect
      ArrayHelper.toIntArray(tableNumbers),
      ArrayHelper.toIntArray(columnNumbers),
      ArrayHelper.toIntArray(formulaNumbers)
    );
  }
    
    
  protected String[] getSubclassTableKeyColumns(int j) {
    return subclassTableKeyColumnClosure[j];
  }

  public String getSubclassTableName(int j) {
    return subclassTableNameClosure[j];
  }

  public int getSubclassTableSpan() {
    return subclassTableNameClosure.length;
  }

  protected boolean isClassOrSuperclassTable(int j) {
    return isClassOrSuperclassTable[j];
  }

  protected boolean isSubclassTableLazy(int j) {
    return subclassTableIsLazyClosure[j];
  }
  
  protected boolean isNullableTable(int j) {
    return isNullableTable[j];
  }
  
  protected boolean isNullableSubclassTable(int j) {
    return isNullableSubclassTable[j];
  }

  public String getPropertyTableName(String propertyName) {
    Integer index = getEntityMetamodel().getPropertyIndexOrNull(propertyName);
    if (index==nullreturn null;
    return qualifiedTableNamespropertyTableNumbersindex.intValue() ] ];
  }
  
  public void postInstantiate() {
    super.postInstantiate();
    if (hasSequentialSelects) {
      String[] entityNames = getSubclassClosure();
      for int i=1; i<entityNames.length; i++ ) {
        Loadable loadable = (LoadablegetFactory().getEntityPersisterentityNames[i] );
        if !loadable.isAbstract() ) { //perhaps not really necessary...
          String sequentialSelect = generateSequentialSelect(loadable);
          sequentialSelectStringsByEntityName.putentityNames[i], sequentialSelect );
        }
      }
    }
  }

  public boolean isMultiTable() {
    return getTableSpan() 1;
  }

  public String[] getConstraintOrderedTableNameClosure() {
    return constraintOrderedTableNames;
  }

  public String[][] getContraintOrderedTableKeyColumnClosure() {
    return constraintOrderedKeyColumnNames;
  }
}