Open Source Repository

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


org/hibernate/hql/ast/tree/IntoClause.java
// $Id: IntoClause.java 15871 2009-02-03 20:48:39Z [email protected] $
package org.hibernate.hql.ast.tree;

import java.util.ArrayList;
import java.util.List;
import java.sql.Types;

import org.hibernate.QueryException;
import org.hibernate.hql.ast.util.DisplayableNode;
import org.hibernate.persister.entity.Queryable;
import org.hibernate.type.Type;
import org.hibernate.util.ArrayHelper;

import antlr.collections.AST;

/**
 * Represents an entity referenced in the INTO clause of an HQL
 * INSERT statement.
 *
 @author Steve Ebersole
 */
public class IntoClause extends HqlSqlWalkerNode implements DisplayableNode {

  private Queryable persister;
  private String columnSpec = "";
  private Type[] types;

  private boolean discriminated;
  private boolean explicitIdInsertion;
  private boolean explicitVersionInsertion;


  public void initialize(Queryable persister) {
    if persister.isAbstract() ) {
      throw new QueryException"cannot insert into abstract class (no table)" );
    }
    this.persister = persister;
    initializeColumns();

    if getWalker().getSessionFactoryHelper().hasPhysicalDiscriminatorColumnpersister ) ) {
      discriminated = true;
      columnSpec += ", " + persister.getDiscriminatorColumnName();
    }

    resetText();
  }

  private void resetText() {
    setText"into " + getTableName() " ( " + columnSpec + " )" );
  }

  public String getTableName() {
    return persister.getSubclassTableName);
  }

  public Queryable getQueryable() {
    return persister;
  }

  public String getEntityName() {
    return persister.getEntityName();
  }

  public Type[] getInsertionTypes() {
    return types;
  }

  public boolean isDiscriminated() {
    return discriminated;
  }

  public boolean isExplicitIdInsertion() {
    return explicitIdInsertion;
  }

  public boolean isExplicitVersionInsertion() {
    return explicitVersionInsertion;
  }

  public void prependIdColumnSpec() {
    columnSpec = persister.getIdentifierColumnNames()[0", " + columnSpec;
    resetText();
  }

  public void prependVersionColumnSpec() {
    columnSpec = persister.getPropertyColumnNamespersister.getVersionProperty() )[0", " + columnSpec;
    resetText();
  }

  public void validateTypes(SelectClause selectClausethrows QueryException {
    Type[] selectTypes = selectClause.getQueryReturnTypes();
    if selectTypes.length != types.length ) {
      throw new QueryException"number of select types did not match those for insert" );
    }

    for int i = 0; i < types.length; i++ ) {
      if !areCompatibletypes[i], selectTypes[i] ) ) {
        throw new QueryException(
                "insertion type [" + types[i"] and selection type [" +
                selectTypes[i"] at position " + i + " are not compatible"
        );
      }
    }

    // otherwise, everything ok.
  }

  /**
   * Returns additional display text for the AST node.
   *
   @return String - The additional display text.
   */
  public String getDisplayText() {
    StringBuffer buf = new StringBuffer();
    buf.append"IntoClause{" );
    buf.append"entityName=" ).appendgetEntityName() );
    buf.append",tableName=" ).appendgetTableName() );
    buf.append",columns={" ).appendcolumnSpec ).append"}" );
    buf.append"}" );
    return buf.toString();
  }

  private void initializeColumns() {
    AST propertySpec = getFirstChild();
    List types = new ArrayList();
    visitPropertySpecNodespropertySpec.getFirstChild(), types );
    this.types = ArrayHelper.toTypeArraytypes );
    columnSpec = columnSpec.substring0, columnSpec.length() );
  }

  private void visitPropertySpecNodes(AST propertyNode, List types) {
    if propertyNode == null ) {
      return;
    }
    // TODO : we really need to be able to deal with component paths here also;
    // this is difficult because the hql-sql grammar expects all those node types
    // to be FromReferenceNodes.  One potential fix here would be to convert the
    // IntoClause to just use a FromClause/FromElement combo (as a child of the
    // InsertStatement) and move all this logic into the InsertStatement.  That's
    // probably the easiest approach (read: least amount of changes to the grammar
    // and code), but just doesn't feel right as then an insert would contain
    // 2 from-clauses
    String name = propertyNode.getText();
    if isSuperclassPropertyname ) ) {
      throw new QueryException"INSERT statements cannot refer to superclass/joined properties [" + name + "]" );
    }

    if name.equalspersister.getIdentifierPropertyName() ) ) {
      explicitIdInsertion = true;
    }

    if persister.isVersioned() ) {
      if name.equalspersister.getPropertyNames()[ persister.getVersionProperty() ] ) ) {
        explicitVersionInsertion = true;
      }
    }

    String[] columnNames = persister.toColumnsname );
    renderColumnscolumnNames );
    types.addpersister.toTypename ) );

    // visit width-first, then depth
    visitPropertySpecNodespropertyNode.getNextSibling(), types );
    visitPropertySpecNodespropertyNode.getFirstChild(), types );
  }

  private void renderColumns(String[] columnNames) {
    for int i = 0; i < columnNames.length; i++ ) {
      columnSpec += columnNames[i", ";
    }
  }

  private boolean isSuperclassProperty(String propertyName) {
    // really there are two situations where it should be ok to allow the insertion
    // into properties defined on a superclass:
    //      1) union-subclass with an abstract root entity
    //      2) discrim-subclass
    //
    // #1 is handled already because of the fact that
    // UnionSubclassPersister alreay always returns 0
    // for this call...
    //
    // we may want to disallow it for discrim-subclass just for
    // consistency-sake (currently does not work anyway)...
    return persister.getSubclassPropertyTableNumberpropertyName != 0;
  }

  /**
   * Determine whether the two types are "assignment compatible".
   *
   @param target The type defined in the into-clause.
   @param source The type defined in the select clause.
   @return True if they are assignment compatible.
   */
  private boolean areCompatible(Type target, Type source) {
    if target.equalssource ) ) {
      // if the types report logical equivalence, return true...
      return true;
    }

    // otherwise, perform a "deep equivalence" check...

    if !target.getReturnedClass().isAssignableFromsource.getReturnedClass() ) ) {
      return false;
    }

    int[] targetDatatypes = target.sqlTypesgetSessionFactoryHelper().getFactory() );
    int[] sourceDatatypes = source.sqlTypesgetSessionFactoryHelper().getFactory() );

    if targetDatatypes.length != sourceDatatypes.length ) {
      return false;
    }

    for int i = 0; i < targetDatatypes.length; i++ ) {
      if !areSqlTypesCompatibletargetDatatypes[i], sourceDatatypes[i] ) ) {
        return false;
      }
    }

    return true;
  }

  private boolean areSqlTypesCompatible(int target, int source) {
    switch target ) {
      case Types.TIMESTAMP:
        return source == Types.DATE || source == Types.TIME || source == Types.TIMESTAMP;
      case Types.DATE:
        return source == Types.DATE || source == Types.TIMESTAMP;
      case Types.TIME:
        return source == Types.TIME || source == Types.TIMESTAMP;
      default:
        return target == source;
    }
  }
}