Example #1
0
  /** Test of the retrieval of FKs. */
  public void testForeignKeyRetrieval() {
    addClassesToSchema(new Class[] {SchemaClass1.class, SchemaClass2.class});

    PersistenceManager pm = pmf.getPersistenceManager();
    RDBMSStoreManager databaseMgr = (RDBMSStoreManager) storeMgr;

    // Retrieve the table for SchemaClass1
    ClassLoaderResolver clr = storeMgr.getNucleusContext().getClassLoaderResolver(null);
    DatastoreClass table1 = databaseMgr.getDatastoreClass(SchemaClass1.class.getName(), clr);

    // Check for the FK using the schema handler
    StoreSchemaHandler handler = databaseMgr.getSchemaHandler();
    Connection con =
        (Connection)
            databaseMgr
                .getConnection(((JDOPersistenceManager) pm).getExecutionContext())
                .getConnection();
    RDBMSTableFKInfo fkInfo =
        (RDBMSTableFKInfo) handler.getSchemaData(con, "foreign-keys", new Object[] {table1});

    // Expecting single FK between SchemaClass1.other and SchemaClass2
    assertEquals(
        "Number of FKs for table " + table1 + " is wrong", 1, fkInfo.getNumberOfChildren());

    // Check the FK details
    ForeignKeyInfo fk = (ForeignKeyInfo) fkInfo.getChild(0);
    assertEquals("FK Name is wrong", "TABLE1_FK1", fk.getProperty("fk_name"));
    assertEquals("PK Table Name is wrong", "SCHEMA_TABLE_2", fk.getProperty("pk_table_name"));
    assertEquals("FK Table Name is wrong", "SCHEMA_TABLE_1", fk.getProperty("fk_table_name"));
    assertEquals("PK Column Name is wrong", "TABLE2_ID", fk.getProperty("pk_column_name"));
    assertEquals("FK Column Name is wrong", "OTHER_ID", fk.getProperty("fk_column_name"));
  }
  /**
   * Method to reserve a block of identities. Note : Only allocates a single id always.
   *
   * @param size The block size
   * @return The reserved block
   */
  public ValueGenerationBlock reserveBlock(long size) {
    // search an Id in the database
    PreparedStatement ps = null;
    ResultSet rs = null;
    RDBMSStoreManager rdbmsMgr = (RDBMSStoreManager) storeMgr;
    SQLController sqlControl = rdbmsMgr.getSQLController();
    try {
      String stmt = getStatement();
      ps = sqlControl.getStatementForUpdate(connection, stmt, false);

      rs = sqlControl.executeStatementQuery(null, connection, stmt, ps);
      if (!rs.next()) {
        return new ValueGenerationBlock(new Object[] {Long.valueOf(1)});
      }

      return new ValueGenerationBlock(new Object[] {Long.valueOf(rs.getLong(1) + 1)});
    } catch (SQLException e) {
      // TODO adds a message correspondent to the exception.
      // we need to work to create user friendly messages
      throw new ValueGenerationException(e.getMessage());
    } finally {
      try {
        if (rs != null) {
          rs.close();
        }
        if (ps != null) {
          sqlControl.closeStatement(connection, ps);
        }
      } catch (SQLException e) {
        // no recoverable error
      }
    }
  }
    protected void setStatement() {
      boolean isEmpty = (value == null || value.size() == 0);
      if (!isEmpty) {
        RDBMSStoreManager storeMgr = stmt.getRDBMSManager();
        valueExpressions = new ArrayList();
        st.append("(");

        boolean hadPrev = false;

        Collection values = value.values();
        for (Iterator it = values.iterator(); it.hasNext(); ) {
          Object current = it.next();
          if (null != current) {
            JavaTypeMapping m =
                storeMgr.getSQLExpressionFactory().getMappingForType(current.getClass(), false);
            SQLExpression expr = storeMgr.getSQLExpressionFactory().newLiteral(stmt, m, current);

            // Append the SQLExpression (should be a literal) for the current element.
            st.append(hadPrev ? "," : "");
            st.append(expr);
            valueExpressions.add(expr);

            hadPrev = true;
          }
        }

        st.append(")");
      }
    }
Example #4
0
  /** Test of the retrieval of indices. */
  public void testIndexRetrieval() {
    addClassesToSchema(new Class[] {SchemaClass1.class, SchemaClass2.class});

    PersistenceManager pm = pmf.getPersistenceManager();
    RDBMSStoreManager databaseMgr = (RDBMSStoreManager) storeMgr;

    // Retrieve the table for SchemaClass1
    ClassLoaderResolver clr = storeMgr.getNucleusContext().getClassLoaderResolver(null);
    DatastoreClass table1 = databaseMgr.getDatastoreClass(SchemaClass1.class.getName(), clr);
    DatastoreClass table2 = databaseMgr.getDatastoreClass(SchemaClass2.class.getName(), clr);

    // Check for the indices using the schema handler
    StoreSchemaHandler handler = databaseMgr.getSchemaHandler();
    Connection con =
        (Connection)
            databaseMgr
                .getConnection(((JDOPersistenceManager) pm).getExecutionContext())
                .getConnection();

    RDBMSTableIndexInfo indexInfo =
        (RDBMSTableIndexInfo) handler.getSchemaData(con, "indices", new Object[] {table1});
    assertEquals(
        "Number of Indices for table " + table1 + " is wrong", 3, indexInfo.getNumberOfChildren());
    Iterator indexIter = indexInfo.getChildren().iterator();
    while (indexIter.hasNext()) {
      IndexInfo index = (IndexInfo) indexIter.next();
      String columnName = (String) index.getProperty("column_name");
      boolean unique = !((Boolean) index.getProperty("non_unique")).booleanValue();
      if (columnName.equals("OTHER_ID")) {
        assertFalse("Index for column " + columnName + " is unique!", unique);
      } else if (columnName.equals("TABLE1_ID1")) {
        assertTrue("Index for column " + columnName + " is not unique!", unique);
      } else if (columnName.equals("TABLE1_ID2")) {
        assertTrue("Index for column " + columnName + " is not unique!", unique);
      } else {
        fail("Unexpected index " + columnName + " for table " + table1);
      }
    }

    indexInfo = (RDBMSTableIndexInfo) handler.getSchemaData(con, "indices", new Object[] {table2});
    assertEquals(
        "Number of Indices for table " + table2 + " is wrong", 2, indexInfo.getNumberOfChildren());
    indexIter = indexInfo.getChildren().iterator();
    while (indexIter.hasNext()) {
      IndexInfo index = (IndexInfo) indexIter.next();
      String columnName = (String) index.getProperty("column_name");
      String indexName = (String) index.getProperty("index_name");
      boolean unique = !((Boolean) index.getProperty("non_unique")).booleanValue();
      if (columnName.equals("VALUE")) {
        assertFalse("Index for column " + columnName + " is unique!", unique);
        assertEquals("Index name for column " + columnName + " is wrong!", "VALUE_IDX", indexName);
      } else if (columnName.equals("TABLE2_ID")) {
        assertTrue("Index for column " + columnName + " is not unique!", unique);
      } else {
        fail("Unexpected index " + columnName + " for table " + table1);
      }
    }
  }
  /* (non-Javadoc)
   * @see org.datanucleus.store.rdbms.sql.method.SQLMethod#getExpression(org.datanucleus.store.rdbms.sql.expression.SQLExpression, java.util.List)
   */
  public SQLExpression getExpression(SQLExpression expr, List<SQLExpression> args) {
    SQLExpression invokedExpr = getInvokedExpression(expr, args, "DAY");

    RDBMSStoreManager storeMgr = stmt.getRDBMSManager();
    JavaTypeMapping mapping = storeMgr.getMappingManager().getMapping(String.class);
    ArrayList funcArgs = new ArrayList();
    funcArgs.add(exprFactory.newLiteral(stmt, mapping, "day"));
    funcArgs.add(invokedExpr);
    return new NumericExpression(stmt, getMappingForClass(int.class), "date_part", funcArgs);
  }
  /**
   * Method to apply any restrictions to the created ResultSet.
   *
   * @param ps The PreparedStatement
   * @param query The query
   * @param applyTimeout Whether to apply the query timeout (if any) direct to the PreparedStatement
   * @throws SQLException Thrown when an error occurs applying the constraints
   */
  public static void prepareStatementForExecution(
      PreparedStatement ps, Query query, boolean applyTimeout) throws SQLException {
    NucleusContext nucleusCtx = query.getExecutionContext().getNucleusContext();
    RDBMSStoreManager storeMgr = (RDBMSStoreManager) query.getStoreManager();
    PersistenceConfiguration conf = nucleusCtx.getPersistenceConfiguration();

    if (applyTimeout) {
      Integer timeout = query.getDatastoreReadTimeoutMillis();
      if (timeout != null && timeout > 0) {
        ps.setQueryTimeout(timeout / 1000);
      }
    }

    // Apply any fetch size
    int fetchSize = 0;
    if (query.getFetchPlan().getFetchSize() > 0) {
      // FetchPlan has a size set so use that
      fetchSize = query.getFetchPlan().getFetchSize();
    }
    if (storeMgr.getDatastoreAdapter().supportsQueryFetchSize(fetchSize)) {
      ps.setFetchSize(fetchSize);
    }

    // Apply any fetch direction
    String fetchDir =
        conf.getStringProperty(RDBMSPropertyNames.PROPERTY_RDBMS_QUERY_FETCH_DIRECTION);
    Object fetchDirExt =
        query.getExtension(RDBMSPropertyNames.PROPERTY_RDBMS_QUERY_FETCH_DIRECTION);
    if (fetchDirExt != null) {
      fetchDir = (String) fetchDirExt;
      if (!fetchDir.equals("forward")
          && !fetchDir.equals("reverse")
          && !fetchDir.equals("unknown")) {
        throw new NucleusUserException(LOCALISER.msg("052512"));
      }
    }

    if (fetchDir.equals("reverse")) {
      ps.setFetchDirection(ResultSet.FETCH_REVERSE);
    } else if (fetchDir.equals("unknown")) {
      ps.setFetchDirection(ResultSet.FETCH_UNKNOWN);
    }

    // Add a limit on the number of rows to include the maximum we may need
    long toExclNo = query.getRangeToExcl();
    if (toExclNo != 0 && toExclNo != Long.MAX_VALUE) {
      if (toExclNo > Integer.MAX_VALUE) {
        // setMaxRows takes an int as input so limit to the correct range
        ps.setMaxRows(Integer.MAX_VALUE);
      } else {
        ps.setMaxRows((int) toExclNo);
      }
    }
  }
 /**
  * Return the SQL statement. TODO Allow this to work in different catalog/schema
  *
  * @return statement
  */
 private String getStatement() {
   RDBMSStoreManager srm = (RDBMSStoreManager) storeMgr;
   StringBuilder stmt = new StringBuilder();
   stmt.append("SELECT max(");
   stmt.append(
       srm.getIdentifierFactory()
           .getIdentifierInAdapterCase((String) properties.get("column-name")));
   stmt.append(") FROM ");
   stmt.append(
       srm.getIdentifierFactory()
           .getIdentifierInAdapterCase((String) properties.get("table-name")));
   return stmt.toString();
 }
  /* (non-Javadoc)
   * @see org.datanucleus.store.rdbms.sql.method.SQLMethod#getExpression(org.datanucleus.store.rdbms.sql.expression.SQLExpression, java.util.List)
   */
  public SQLExpression getExpression(SQLExpression expr, List args) {
    if (!(expr instanceof TypeConverterExpression)
        || !(((TypeConverterExpression) expr).getDelegate() instanceof TemporalExpression)) {
      throw new NucleusException(Localiser.msg("060001", "getHour", expr));
    }

    RDBMSStoreManager storeMgr = stmt.getRDBMSManager();
    JavaTypeMapping mapping = storeMgr.getMappingManager().getMapping(String.class);
    SQLExpression hh = exprFactory.newLiteral(stmt, mapping, "HH24");

    List funcArgs = new ArrayList();
    funcArgs.add(expr);
    funcArgs.add(hh);
    List funcArgs2 = new ArrayList();
    funcArgs2.add(new StringExpression(stmt, mapping, "TO_CHAR", funcArgs));
    return new NumericExpression(stmt, getMappingForClass(int.class), "TO_NUMBER", funcArgs2);
  }
  /**
   * Method to create the repository for ids to be stored.
   *
   * @return Whether it was created successfully.
   */
  protected boolean createRepository() {
    RDBMSStoreManager srm = (RDBMSStoreManager) storeMgr;
    if (!srm.isAutoCreateTables()) {
      throw new NucleusUserException(LOCALISER.msg("040011", sequenceTable));
    }

    try {
      if (sequenceTable == null) {
        initialiseSequenceTable();
      }
      sequenceTable.exists((Connection) connection.getConnection(), true);
      repositoryExists = true;
      return true;
    } catch (SQLException sqle) {
      throw new ValueGenerationException(
          "Exception thrown calling table.exists() for " + sequenceTable, sqle);
    }
  }
  /** Method to initialise the sequence table used for storing the sequence values. */
  protected void initialiseSequenceTable() {
    // Set catalog/schema name (using properties, and if not specified using the values for the
    // table)
    String catalogName = properties.getProperty("sequence-catalog-name");
    if (catalogName == null) {
      catalogName = properties.getProperty("catalog-name");
    }
    String schemaName = properties.getProperty("sequence-schema-name");
    if (schemaName == null) {
      schemaName = properties.getProperty("schema-name");
    }
    String tableName =
        (properties.getProperty("sequence-table-name") == null
            ? DEFAULT_TABLE_NAME
            : properties.getProperty("sequence-table-name"));

    RDBMSStoreManager storeMgr = (RDBMSStoreManager) this.storeMgr;
    DatastoreAdapter dba = storeMgr.getDatastoreAdapter();
    DatastoreIdentifier identifier = storeMgr.getIdentifierFactory().newTableIdentifier(tableName);
    if (dba.supportsOption(DatastoreAdapter.CATALOGS_IN_TABLE_DEFINITIONS) && catalogName != null) {
      identifier.setCatalogName(catalogName);
    }
    if (dba.supportsOption(DatastoreAdapter.SCHEMAS_IN_TABLE_DEFINITIONS) && schemaName != null) {
      identifier.setSchemaName(schemaName);
    }

    DatastoreClass table = storeMgr.getDatastoreClass(identifier);
    if (table != null) {
      sequenceTable = (SequenceTable) table;
    } else {
      String sequenceNameColumnName = DEFAULT_SEQUENCE_COLUMN_NAME;
      String nextValColumnName = DEFAULT_NEXTVALUE_COLUMN_NAME;
      if (properties.getProperty("sequence-name-column-name") != null) {
        sequenceNameColumnName = properties.getProperty("sequence-name-column-name");
      }
      if (properties.getProperty("sequence-nextval-column-name") != null) {
        nextValColumnName = properties.getProperty("sequence-nextval-column-name");
      }
      sequenceTable =
          new SequenceTable(identifier, storeMgr, sequenceNameColumnName, nextValColumnName);
      sequenceTable.initialize(storeMgr.getNucleusContext().getClassLoaderResolver(null));
    }
  }
Example #11
0
  /** Test of the retrieval of PKs. */
  public void testPrimaryKeyRetrieval() {
    addClassesToSchema(new Class[] {SchemaClass1.class, SchemaClass2.class});

    PersistenceManager pm = pmf.getPersistenceManager();
    RDBMSStoreManager databaseMgr = (RDBMSStoreManager) storeMgr;

    // Retrieve the table for SchemaClass1
    ClassLoaderResolver clr = storeMgr.getNucleusContext().getClassLoaderResolver(null);
    DatastoreClass table1 = databaseMgr.getDatastoreClass(SchemaClass1.class.getName(), clr);
    DatastoreClass table2 = databaseMgr.getDatastoreClass(SchemaClass2.class.getName(), clr);

    // Check for the FK using the schema handler
    StoreSchemaHandler handler = databaseMgr.getSchemaHandler();
    Connection con =
        (Connection)
            databaseMgr
                .getConnection(((JDOPersistenceManager) pm).getExecutionContext())
                .getConnection();
    RDBMSTablePKInfo pkInfo1 =
        (RDBMSTablePKInfo) handler.getSchemaData(con, "primary-keys", new Object[] {table1});
    RDBMSTablePKInfo pkInfo2 =
        (RDBMSTablePKInfo) handler.getSchemaData(con, "primary-keys", new Object[] {table2});

    // Expecting 2 PK columns for SchemaClass1
    // TODO Enable checks on the PK name (when JDBC drivers return it correctly)
    assertEquals(
        "Number of PKs for table " + table1 + " is wrong", 2, pkInfo1.getNumberOfChildren());
    PrimaryKeyInfo pk = (PrimaryKeyInfo) pkInfo1.getChild(0);
    assertEquals("Column Name is wrong", "TABLE1_ID1", pk.getProperty("column_name"));
    //        assertEquals("PK Name is wrong", "TABLE1_PK", pk.getProperty("pk_name"));
    pk = (PrimaryKeyInfo) pkInfo1.getChild(1);
    assertEquals("Column Name is wrong", "TABLE1_ID2", pk.getProperty("column_name"));
    //        assertEquals("PK Name is wrong", "TABLE1_PK", pk.getProperty("pk_name"));

    // Expecting 1 PK column for SchemaClass
    assertEquals(
        "Number of PKs for table " + table1 + " is wrong", 1, pkInfo2.getNumberOfChildren());
    pk = (PrimaryKeyInfo) pkInfo2.getChild(0);
    assertEquals("Column Name is wrong", "TABLE2_ID", pk.getProperty("column_name"));
    //        assertEquals("PK Name is wrong", "TABLE2_PK", pk.getProperty("pk_name"));
  }
Example #12
0
  /** Test of the retrieval of columns. */
  public void testColumnRetrieval() {
    addClassesToSchema(new Class[] {SchemaClass1.class, SchemaClass2.class});

    PersistenceManager pm = pmf.getPersistenceManager();
    RDBMSStoreManager databaseMgr = (RDBMSStoreManager) storeMgr;
    StoreSchemaHandler handler = databaseMgr.getSchemaHandler();
    ClassLoaderResolver clr = storeMgr.getNucleusContext().getClassLoaderResolver(null);
    Connection con =
        (Connection)
            databaseMgr
                .getConnection(((JDOPersistenceManager) pm).getExecutionContext())
                .getConnection();

    // Retrieve and check the table for SchemaClass1
    DatastoreClass table1 = databaseMgr.getDatastoreClass(SchemaClass1.class.getName(), clr);
    RDBMSTableInfo tableInfo1 =
        (RDBMSTableInfo) handler.getSchemaData(con, "columns", new Object[] {table1});
    assertEquals(
        "Number of columns for table " + table1 + " is wrong", 4, tableInfo1.getNumberOfChildren());
    Iterator colsIter = tableInfo1.getChildren().iterator();
    Collection colNamesPresent = new HashSet();
    colNamesPresent.add("TABLE1_ID1");
    colNamesPresent.add("TABLE1_ID2");
    colNamesPresent.add("NAME");
    colNamesPresent.add("OTHER_ID");
    while (colsIter.hasNext()) {
      RDBMSColumnInfo colInfo = (RDBMSColumnInfo) colsIter.next();
      if (colInfo.getColumnName().equals("TABLE1_ID1")) {
        colNamesPresent.remove(colInfo.getColumnName());
      }
      if (colInfo.getColumnName().equals("TABLE1_ID2")) {
        colNamesPresent.remove(colInfo.getColumnName());
      }
      if (colInfo.getColumnName().equals("NAME")) {
        colNamesPresent.remove(colInfo.getColumnName());
      }
      if (colInfo.getColumnName().equals("OTHER_ID")) {
        colNamesPresent.remove(colInfo.getColumnName());
      }
    }
    assertTrue(
        "Some columns expected were not present in the datastore table : "
            + StringUtils.collectionToString(colNamesPresent),
        colNamesPresent.size() == 0);

    // Retrieve and check the table for SchemaClass2
    DatastoreClass table2 = databaseMgr.getDatastoreClass(SchemaClass2.class.getName(), clr);
    RDBMSTableInfo tableInfo2 =
        (RDBMSTableInfo) handler.getSchemaData(con, "columns", new Object[] {table2});
    assertEquals(
        "Number of columns for table " + table2 + " is wrong", 3, tableInfo2.getNumberOfChildren());
    colsIter = tableInfo2.getChildren().iterator();
    colNamesPresent.clear();
    colNamesPresent.add("TABLE2_ID");
    colNamesPresent.add("NAME");
    colNamesPresent.add("VALUE");
    while (colsIter.hasNext()) {
      RDBMSColumnInfo colInfo = (RDBMSColumnInfo) colsIter.next();
      if (colInfo.getColumnName().equals("TABLE2_ID")) {
        colNamesPresent.remove(colInfo.getColumnName());
      }
      if (colInfo.getColumnName().equals("NAME")) {
        colNamesPresent.remove(colInfo.getColumnName());
        assertEquals(
            "Length of column " + colInfo.getColumnName() + " has incorrect length",
            20,
            colInfo.getColumnSize());
      }
      if (colInfo.getColumnName().equals("VALUE")) {
        colNamesPresent.remove(colInfo.getColumnName());
      }
    }
    assertTrue(
        "Some columns expected were not present in the datastore table : "
            + StringUtils.collectionToString(colNamesPresent),
        colNamesPresent.size() == 0);

    // Now check retrieval of a column for a table
    RDBMSColumnInfo colInfo =
        (RDBMSColumnInfo) handler.getSchemaData(con, "column", new Object[] {table2, "VALUE"});
    assertNotNull("Column VALUE for table " + table2 + " was not found", colInfo);
    assertEquals("Column name is wrong", "VALUE", colInfo.getColumnName());
  }
  /**
   * Method to return a statement selecting the candidate table(s) required to cover all possible
   * types for this candidates inheritance strategy.
   *
   * @param storeMgr RDBMS StoreManager
   * @param parentStmt Parent statement (if there is one)
   * @param cmd Metadata for the class
   * @param clsMapping Mapping for the results of the statement
   * @param ec ExecutionContext
   * @param candidateCls Candidate class
   * @param subclasses Whether to create a statement for subclasses of the candidate too
   * @param result The result clause
   * @param candidateAlias alias for the candidate (if any)
   * @param candidateTableGroupName TableGroup name for the candidate (if any)
   * @return The SQLStatement
   * @throws NucleusException if there are no tables for concrete classes in this query (hence would
   *     return null)
   */
  public static SQLStatement getStatementForCandidates(
      RDBMSStoreManager storeMgr,
      SQLStatement parentStmt,
      AbstractClassMetaData cmd,
      StatementClassMapping clsMapping,
      ExecutionContext ec,
      Class candidateCls,
      boolean subclasses,
      String result,
      String candidateAlias,
      String candidateTableGroupName) {
    SQLStatement stmt = null;

    DatastoreIdentifier candidateAliasId = null;
    if (candidateAlias != null) {
      candidateAliasId = storeMgr.getIdentifierFactory().newTableIdentifier(candidateAlias);
    }

    ClassLoaderResolver clr = ec.getClassLoaderResolver();
    List<DatastoreClass> candidateTables = new ArrayList<DatastoreClass>();
    if (cmd.getInheritanceMetaData().getStrategy() == InheritanceStrategy.COMPLETE_TABLE) {
      DatastoreClass candidateTable = storeMgr.getDatastoreClass(cmd.getFullClassName(), clr);
      if (candidateTable != null) {
        candidateTables.add(candidateTable);
      }
      if (subclasses) {
        Collection<String> subclassNames =
            storeMgr.getSubClassesForClass(cmd.getFullClassName(), subclasses, clr);
        if (subclassNames != null) {
          Iterator<String> subclassIter = subclassNames.iterator();
          while (subclassIter.hasNext()) {
            String subclassName = subclassIter.next();
            DatastoreClass tbl = storeMgr.getDatastoreClass(subclassName, clr);
            if (tbl != null) {
              candidateTables.add(tbl);
            }
          }
        }
      }

      Iterator<DatastoreClass> iter = candidateTables.iterator();
      int maxClassNameLength = cmd.getFullClassName().length();
      while (iter.hasNext()) {
        DatastoreClass cls = iter.next();
        String className = cls.getType();
        if (className.length() > maxClassNameLength) {
          maxClassNameLength = className.length();
        }
      }

      iter = candidateTables.iterator();
      while (iter.hasNext()) {
        DatastoreClass cls = iter.next();

        SQLStatement tblStmt =
            new SQLStatement(parentStmt, storeMgr, cls, candidateAliasId, candidateTableGroupName);
        tblStmt.setClassLoaderResolver(clr);
        tblStmt.setCandidateClassName(cls.getType());

        // Add SELECT of dummy column accessible as "NUCLEUS_TYPE" containing the classname
        JavaTypeMapping m = storeMgr.getMappingManager().getMapping(String.class);
        String nuctypeName = cls.getType();
        if (maxClassNameLength > nuctypeName.length()) {
          nuctypeName = StringUtils.leftAlignedPaddedString(nuctypeName, maxClassNameLength);
        }
        StringLiteral lit = new StringLiteral(tblStmt, m, nuctypeName, null);
        tblStmt.select(lit, UnionStatementGenerator.NUC_TYPE_COLUMN);

        if (stmt == null) {
          stmt = tblStmt;
        } else {
          stmt.union(tblStmt);
        }
      }
      if (clsMapping != null) {
        clsMapping.setNucleusTypeColumnName(UnionStatementGenerator.NUC_TYPE_COLUMN);
      }
    } else {
      // "new-table", "superclass-table", "subclass-table"
      List<Class> candidateClasses = new ArrayList<Class>();
      if (ClassUtils.isReferenceType(candidateCls)) {
        // Persistent interface, so find all persistent implementations
        String[] clsNames =
            storeMgr
                .getNucleusContext()
                .getMetaDataManager()
                .getClassesImplementingInterface(candidateCls.getName(), clr);
        for (int i = 0; i < clsNames.length; i++) {
          Class cls = clr.classForName(clsNames[i]);
          DatastoreClass table = storeMgr.getDatastoreClass(clsNames[i], clr);
          candidateClasses.add(cls);
          candidateTables.add(table);
          AbstractClassMetaData implCmd =
              storeMgr.getNucleusContext().getMetaDataManager().getMetaDataForClass(cls, clr);
          if (implCmd.getIdentityType() != cmd.getIdentityType()) {
            throw new NucleusUserException(
                "You are querying an interface ("
                    + cmd.getFullClassName()
                    + ") "
                    + "yet one of its implementations ("
                    + implCmd.getFullClassName()
                    + ") "
                    + " uses a different identity type!");
          } else if (cmd.getIdentityType() == IdentityType.APPLICATION) {
            if (cmd.getPKMemberPositions().length != implCmd.getPKMemberPositions().length) {
              throw new NucleusUserException(
                  "You are querying an interface ("
                      + cmd.getFullClassName()
                      + ") "
                      + "yet one of its implementations ("
                      + implCmd.getFullClassName()
                      + ") "
                      + " has a different number of PK members!");
            }
          }
        }
      } else {
        DatastoreClass candidateTable = storeMgr.getDatastoreClass(cmd.getFullClassName(), clr);
        if (candidateTable != null) {
          // Candidate has own table
          candidateClasses.add(candidateCls);
          candidateTables.add(candidateTable);
        } else {
          // Candidate stored in subclass tables
          AbstractClassMetaData[] cmds = storeMgr.getClassesManagingTableForClass(cmd, clr);
          if (cmds != null && cmds.length > 0) {
            for (int i = 0; i < cmds.length; i++) {
              DatastoreClass table = storeMgr.getDatastoreClass(cmds[i].getFullClassName(), clr);
              Class cls = clr.classForName(cmds[i].getFullClassName());
              candidateClasses.add(cls);
              candidateTables.add(table);
            }
          } else {
            throw new UnsupportedOperationException(
                "No tables for query of " + cmd.getFullClassName());
          }
        }
      }

      for (int i = 0; i < candidateTables.size(); i++) {
        DatastoreClass tbl = candidateTables.get(i);
        Class cls = candidateClasses.get(i);
        StatementGenerator stmtGen = null;
        if (tbl.getDiscriminatorMapping(true) != null
            || QueryUtils.resultHasOnlyAggregates(result)) {
          // Either has a discriminator, or only selecting aggregates so need single select
          stmtGen =
              new DiscriminatorStatementGenerator(
                  storeMgr, clr, cls, subclasses, candidateAliasId, candidateTableGroupName);
          stmtGen.setOption(StatementGenerator.OPTION_RESTRICT_DISCRIM);
        } else {
          stmtGen =
              new UnionStatementGenerator(
                  storeMgr, clr, cls, subclasses, candidateAliasId, candidateTableGroupName);
          if (result == null) {
            // Returning one row per candidate so include distinguisher column
            stmtGen.setOption(StatementGenerator.OPTION_SELECT_NUCLEUS_TYPE);
            clsMapping.setNucleusTypeColumnName(UnionStatementGenerator.NUC_TYPE_COLUMN);
          }
        }
        stmtGen.setParentStatement(parentStmt);
        SQLStatement tblStmt = stmtGen.getStatement();

        if (stmt == null) {
          stmt = tblStmt;
        } else {
          stmt.union(tblStmt);
        }
      }
    }

    return stmt;
  }
  /**
   * Method to return an expression for Map.containsValue using a subquery "EXISTS". This is for use
   * when there are "!contains" or "OR" operations in the filter. Creates the following SQL,
   *
   * <ul>
   *   <li><b>Map using join table</b>
   *       <pre>
   * SELECT 1 FROM JOIN_TBL A0_SUB
   * WHERE A0_SUB.JOIN_OWN_ID = A0.ID AND A0_SUB.JOIN_VAL_ID = {valExpr}
   * </pre>
   *   <li><b>Map with key stored in value</b>
   *       <pre>
   * SELECT 1 FROM VAL_TABLE A0_SUB INNER JOIN KEY_TBL B0 ON ...
   * WHERE B0.JOIN_OWN_ID = A0.ID AND A0_SUB.ID = {valExpr}
   * </pre>
   *   <li><b>Map of value stored in key</b>
   *       <pre>
   * SELECT 1 FROM VAL_TABLE A0_SUB
   * WHERE A0_SUB.OWN_ID = A0.ID AND A0_SUB.ID = {valExpr}
   * </pre>
   * </ul>
   *
   * and returns a BooleanSubqueryExpression ("EXISTS (subquery)")
   *
   * @param mapExpr Map expression
   * @param valExpr Expression for the value
   * @return Contains expression
   */
  protected SQLExpression containsAsSubquery(MapExpression mapExpr, SQLExpression valExpr) {
    boolean valIsUnbound = (valExpr instanceof UnboundExpression);
    String varName = null;
    if (valIsUnbound) {
      varName = ((UnboundExpression) valExpr).getVariableName();
      NucleusLogger.QUERY.debug(
          "map.containsValue binding unbound variable " + varName + " using SUBQUERY");
      // TODO What if the variable is declared as a subtype, handle this see
      // CollectionContainsMethod
    }

    RDBMSStoreManager storeMgr = stmt.getRDBMSManager();
    MetaDataManager mmgr = storeMgr.getMetaDataManager();
    AbstractMemberMetaData mmd = mapExpr.getJavaTypeMapping().getMemberMetaData();
    AbstractClassMetaData valCmd = mmd.getMap().getValueClassMetaData(clr, mmgr);
    MapTable joinTbl = (MapTable) storeMgr.getTable(mmd);
    SQLStatement subStmt = null;
    if (mmd.getMap().getMapType() == MapType.MAP_TYPE_JOIN) {
      // JoinTable Map
      if (valCmd == null) {
        // Map<?, Non-PC>
        subStmt = new SQLStatement(stmt, storeMgr, joinTbl, null, null);
        subStmt.setClassLoaderResolver(clr);
        JavaTypeMapping oneMapping = storeMgr.getMappingManager().getMapping(Integer.class);
        subStmt.select(exprFactory.newLiteral(subStmt, oneMapping, 1), null);

        // Restrict to map owner
        JavaTypeMapping ownerMapping = ((JoinTable) joinTbl).getOwnerMapping();
        SQLExpression ownerExpr =
            exprFactory.newExpression(subStmt, subStmt.getPrimaryTable(), ownerMapping);
        SQLExpression ownerIdExpr =
            exprFactory.newExpression(
                stmt, mapExpr.getSQLTable(), mapExpr.getSQLTable().getTable().getIdMapping());
        subStmt.whereAnd(ownerExpr.eq(ownerIdExpr), true);

        if (valIsUnbound) {
          // Bind the variable in the QueryGenerator
          valExpr =
              exprFactory.newExpression(
                  subStmt, subStmt.getPrimaryTable(), joinTbl.getValueMapping());
          stmt.getQueryGenerator()
              .bindVariable(varName, null, valExpr.getSQLTable(), valExpr.getJavaTypeMapping());
        } else {
          // Add restrict to value
          SQLExpression valIdExpr =
              exprFactory.newExpression(
                  subStmt, subStmt.getPrimaryTable(), joinTbl.getValueMapping());
          subStmt.whereAnd(valIdExpr.eq(valExpr), true);
        }
      } else {
        // Map<?, PC>
        DatastoreClass valTbl = storeMgr.getDatastoreClass(mmd.getMap().getValueType(), clr);
        subStmt = new SQLStatement(stmt, storeMgr, valTbl, null, null);
        subStmt.setClassLoaderResolver(clr);
        JavaTypeMapping oneMapping = storeMgr.getMappingManager().getMapping(Integer.class);
        subStmt.select(exprFactory.newLiteral(subStmt, oneMapping, 1), null);

        // Join to join table
        SQLTable joinSqlTbl =
            subStmt.innerJoin(
                subStmt.getPrimaryTable(),
                valTbl.getIdMapping(),
                joinTbl,
                null,
                joinTbl.getValueMapping(),
                null,
                null);

        // Restrict to map owner
        JavaTypeMapping ownerMapping = joinTbl.getOwnerMapping();
        SQLExpression ownerExpr = exprFactory.newExpression(subStmt, joinSqlTbl, ownerMapping);
        SQLExpression ownerIdExpr =
            exprFactory.newExpression(
                stmt, mapExpr.getSQLTable(), mapExpr.getSQLTable().getTable().getIdMapping());
        subStmt.whereAnd(ownerExpr.eq(ownerIdExpr), true);

        if (valIsUnbound) {
          // Bind the variable in the QueryGenerator
          valExpr =
              exprFactory.newExpression(subStmt, subStmt.getPrimaryTable(), valTbl.getIdMapping());
          stmt.getQueryGenerator()
              .bindVariable(varName, valCmd, valExpr.getSQLTable(), valExpr.getJavaTypeMapping());
        } else {
          // Add restrict to value
          SQLExpression valIdExpr =
              exprFactory.newExpression(subStmt, subStmt.getPrimaryTable(), valTbl.getIdMapping());
          subStmt.whereAnd(valIdExpr.eq(valExpr), true);
        }
      }
    } else if (mmd.getMap().getMapType() == MapType.MAP_TYPE_KEY_IN_VALUE) {
      // Key stored in value table
      DatastoreClass valTbl = storeMgr.getDatastoreClass(mmd.getMap().getValueType(), clr);
      JavaTypeMapping ownerMapping = null;
      if (mmd.getMappedBy() != null) {
        ownerMapping = valTbl.getMemberMapping(valCmd.getMetaDataForMember(mmd.getMappedBy()));
      } else {
        ownerMapping = valTbl.getExternalMapping(mmd, MappingConsumer.MAPPING_TYPE_EXTERNAL_FK);
      }

      subStmt = new SQLStatement(stmt, storeMgr, valTbl, null, null);
      subStmt.setClassLoaderResolver(clr);
      JavaTypeMapping oneMapping = storeMgr.getMappingManager().getMapping(Integer.class);
      subStmt.select(exprFactory.newLiteral(subStmt, oneMapping, 1), null);

      // Restrict to map owner (on value table)
      SQLExpression ownerExpr =
          exprFactory.newExpression(subStmt, subStmt.getPrimaryTable(), ownerMapping);
      SQLExpression ownerIdExpr =
          exprFactory.newExpression(
              stmt, mapExpr.getSQLTable(), mapExpr.getSQLTable().getTable().getIdMapping());
      subStmt.whereAnd(ownerExpr.eq(ownerIdExpr), true);

      if (valIsUnbound) {
        // Bind the variable in the QueryGenerator
        valExpr =
            exprFactory.newExpression(subStmt, subStmt.getPrimaryTable(), valTbl.getIdMapping());
        stmt.getQueryGenerator()
            .bindVariable(varName, valCmd, valExpr.getSQLTable(), valExpr.getJavaTypeMapping());
      } else {
        // Add restrict to value
        JavaTypeMapping valMapping = valTbl.getIdMapping();
        SQLExpression valIdExpr =
            exprFactory.newExpression(subStmt, subStmt.getPrimaryTable(), valMapping);
        subStmt.whereAnd(valIdExpr.eq(valExpr), true);
      }
    } else if (mmd.getMap().getMapType() == MapType.MAP_TYPE_VALUE_IN_KEY) {
      AbstractClassMetaData keyCmd = mmd.getMap().getKeyClassMetaData(clr, mmgr);
      DatastoreClass keyTbl = storeMgr.getDatastoreClass(mmd.getMap().getKeyType(), clr);
      JavaTypeMapping ownerMapping = null;
      if (mmd.getMappedBy() != null) {
        ownerMapping = keyTbl.getMemberMapping(keyCmd.getMetaDataForMember(mmd.getMappedBy()));
      } else {
        ownerMapping = keyTbl.getExternalMapping(mmd, MappingConsumer.MAPPING_TYPE_EXTERNAL_FK);
      }

      AbstractMemberMetaData keyValMmd =
          keyCmd.getMetaDataForMember(mmd.getValueMetaData().getMappedBy());
      if (valCmd == null) {
        subStmt = new SQLStatement(stmt, storeMgr, keyTbl, null, null);
        subStmt.setClassLoaderResolver(clr);
        JavaTypeMapping oneMapping = storeMgr.getMappingManager().getMapping(Integer.class);
        subStmt.select(exprFactory.newLiteral(subStmt, oneMapping, 1), null);

        // Restrict to map owner (on key table)
        SQLExpression ownerExpr =
            exprFactory.newExpression(subStmt, subStmt.getPrimaryTable(), ownerMapping);
        SQLExpression ownerIdExpr =
            exprFactory.newExpression(
                stmt, mapExpr.getSQLTable(), mapExpr.getSQLTable().getTable().getIdMapping());
        subStmt.whereAnd(ownerExpr.eq(ownerIdExpr), true);

        if (valIsUnbound) {
          // Bind the variable in the QueryGenerator
          valExpr =
              exprFactory.newExpression(
                  subStmt, subStmt.getPrimaryTable(), keyTbl.getMemberMapping(keyValMmd));
          stmt.getQueryGenerator()
              .bindVariable(varName, null, valExpr.getSQLTable(), valExpr.getJavaTypeMapping());
        } else {
          // Add restrict to value
          JavaTypeMapping valMapping = keyTbl.getMemberMapping(keyValMmd);
          SQLExpression valIdExpr =
              exprFactory.newExpression(subStmt, subStmt.getPrimaryTable(), valMapping);
          subStmt.whereAnd(valIdExpr.eq(valExpr), true);
        }
      } else {
        DatastoreClass valTbl = storeMgr.getDatastoreClass(mmd.getMap().getValueType(), clr);
        subStmt = new SQLStatement(stmt, storeMgr, valTbl, null, null);
        subStmt.setClassLoaderResolver(clr);
        JavaTypeMapping oneMapping = storeMgr.getMappingManager().getMapping(Integer.class);
        subStmt.select(exprFactory.newLiteral(subStmt, oneMapping, 1), null);

        // Join to key table
        SQLTable keySqlTbl =
            subStmt.innerJoin(
                subStmt.getPrimaryTable(),
                valTbl.getIdMapping(),
                keyTbl,
                null,
                keyTbl.getMemberMapping(keyValMmd),
                null,
                null);

        // Restrict to map owner (on key table)
        SQLExpression ownerExpr = exprFactory.newExpression(subStmt, keySqlTbl, ownerMapping);
        SQLExpression ownerIdExpr =
            exprFactory.newExpression(
                stmt, mapExpr.getSQLTable(), mapExpr.getSQLTable().getTable().getIdMapping());
        subStmt.whereAnd(ownerExpr.eq(ownerIdExpr), true);

        if (valIsUnbound) {
          // Bind the variable in the QueryGenerator
          valExpr =
              exprFactory.newExpression(subStmt, subStmt.getPrimaryTable(), valTbl.getIdMapping());
          stmt.getQueryGenerator()
              .bindVariable(varName, valCmd, valExpr.getSQLTable(), valExpr.getJavaTypeMapping());
        } else {
          // Add restrict to value
          SQLExpression valIdExpr =
              exprFactory.newExpression(subStmt, subStmt.getPrimaryTable(), valTbl.getIdMapping());
          subStmt.whereAnd(valIdExpr.eq(valExpr), true);
        }
      }
    }

    return new BooleanSubqueryExpression(stmt, "EXISTS", subStmt);
  }
  /**
   * Method to return an expression for Map.containsValue using INNER JOIN to the element. This is
   * only for use when there are no "!containsValue" and no "OR" operations. Creates SQL by adding
   * INNER JOIN to the join table (where it exists), and also to the value table adding an AND
   * condition on the value (with value of the valueExpr). Returns a BooleanExpression "TRUE" (since
   * the INNER JOIN will guarantee if the value is contained of not).
   *
   * @param mapExpr Map expression
   * @param valExpr Expression for the value
   * @return Contains expression
   */
  protected SQLExpression containsAsInnerJoin(MapExpression mapExpr, SQLExpression valExpr) {
    boolean valIsUnbound = (valExpr instanceof UnboundExpression);
    String varName = null;
    String valAlias = null;
    if (valIsUnbound) {
      varName = ((UnboundExpression) valExpr).getVariableName();
      NucleusLogger.QUERY.debug(
          "map.containsValue("
              + valExpr
              + ") binding unbound variable "
              + varName
              + " using INNER JOIN");
      // TODO What if the variable is declared as a subtype, handle this see
      // CollectionContainsMethod
    } else if (!stmt.getQueryGenerator().hasExplicitJoins()) {
      JoinType joinType = stmt.getJoinTypeForTable(valExpr.getSQLTable());
      if (joinType == JoinType.CROSS_JOIN) {
        // Value is currently joined via CROSS JOIN, so remove it (and use INNER JOIN below)
        valAlias = stmt.removeCrossJoin(valExpr.getSQLTable());
        valIsUnbound = true;
        NucleusLogger.QUERY.debug(
            "map.containsValue("
                + valExpr
                + ") was previously bound as CROSS JOIN but changing to INNER JOIN");
      }

      // TODO If owner is joined via CROSS JOIN and value is already present then remove CROSS JOIN
      // and join via INNER JOIN
    }

    RDBMSStoreManager storeMgr = stmt.getRDBMSManager();
    MetaDataManager mmgr = storeMgr.getMetaDataManager();
    AbstractMemberMetaData mmd = mapExpr.getJavaTypeMapping().getMemberMetaData();
    AbstractClassMetaData valCmd = mmd.getMap().getValueClassMetaData(clr, mmgr);
    if (mmd.getMap().getMapType() == MapType.MAP_TYPE_JOIN) {
      // Map formed in join table - add join to join table, then to value table (if present)
      MapTable mapTbl = (MapTable) storeMgr.getTable(mmd);
      SQLTable joinSqlTbl =
          stmt.innerJoin(
              mapExpr.getSQLTable(),
              mapExpr.getSQLTable().getTable().getIdMapping(),
              mapTbl,
              null,
              mapTbl.getOwnerMapping(),
              null,
              null);
      if (valCmd != null) {
        if (valIsUnbound) {
          DatastoreClass valTbl = storeMgr.getDatastoreClass(valCmd.getFullClassName(), clr);
          SQLTable valSqlTbl =
              stmt.innerJoin(
                  joinSqlTbl,
                  mapTbl.getValueMapping(),
                  valTbl,
                  valAlias,
                  valTbl.getIdMapping(),
                  null,
                  null);

          // Bind the variable in the QueryGenerator
          valExpr = exprFactory.newExpression(stmt, valSqlTbl, valSqlTbl.getTable().getIdMapping());
          stmt.getQueryGenerator()
              .bindVariable(varName, valCmd, valExpr.getSQLTable(), valExpr.getJavaTypeMapping());
        } else {
          // Add restrict to value
          SQLExpression valIdExpr =
              exprFactory.newExpression(stmt, joinSqlTbl, mapTbl.getValueMapping());
          stmt.whereAnd(valIdExpr.eq(valExpr), true);
        }
      } else {
        if (valIsUnbound) {
          // Bind the variable in the QueryGenerator
          valExpr = exprFactory.newExpression(stmt, joinSqlTbl, mapTbl.getValueMapping());
          stmt.getQueryGenerator()
              .bindVariable(varName, null, valExpr.getSQLTable(), valExpr.getJavaTypeMapping());
        } else {
          // Add restrict to value
          SQLExpression valIdExpr =
              exprFactory.newExpression(stmt, joinSqlTbl, mapTbl.getValueMapping());
          stmt.whereAnd(valIdExpr.eq(valExpr), true);
        }
      }
    } else if (mmd.getMap().getMapType() == MapType.MAP_TYPE_KEY_IN_VALUE) {
      // Map formed in value table - add join to value table
      DatastoreClass valTbl = storeMgr.getDatastoreClass(valCmd.getFullClassName(), clr);
      JavaTypeMapping ownerMapping = null;
      if (mmd.getMappedBy() != null) {
        ownerMapping = valTbl.getMemberMapping(valCmd.getMetaDataForMember(mmd.getMappedBy()));
      } else {
        ownerMapping = valTbl.getExternalMapping(mmd, MappingConsumer.MAPPING_TYPE_EXTERNAL_FK);
      }
      SQLTable valSqlTbl =
          stmt.innerJoin(
              mapExpr.getSQLTable(),
              mapExpr.getSQLTable().getTable().getIdMapping(),
              valTbl,
              valAlias,
              ownerMapping,
              null,
              null);

      if (valIsUnbound) {
        // Bind the variable in the QueryGenerator
        valExpr = exprFactory.newExpression(stmt, valSqlTbl, valTbl.getIdMapping());
        stmt.getQueryGenerator()
            .bindVariable(varName, valCmd, valExpr.getSQLTable(), valExpr.getJavaTypeMapping());
      } else {
        // Add restrict to value
        SQLExpression valIdExpr = exprFactory.newExpression(stmt, valSqlTbl, valTbl.getIdMapping());
        stmt.whereAnd(valIdExpr.eq(valExpr), true);
      }
    } else if (mmd.getMap().getMapType() == MapType.MAP_TYPE_VALUE_IN_KEY) {
      // Map formed in key table - add join to key table then to value table
      AbstractClassMetaData keyCmd = mmd.getMap().getKeyClassMetaData(clr, mmgr);
      DatastoreClass keyTbl = storeMgr.getDatastoreClass(keyCmd.getFullClassName(), clr);
      AbstractMemberMetaData keyValMmd =
          keyCmd.getMetaDataForMember(mmd.getValueMetaData().getMappedBy());
      JavaTypeMapping ownerMapping = null;
      if (mmd.getMappedBy() != null) {
        ownerMapping = keyTbl.getMemberMapping(keyCmd.getMetaDataForMember(mmd.getMappedBy()));
      } else {
        ownerMapping = keyTbl.getExternalMapping(mmd, MappingConsumer.MAPPING_TYPE_EXTERNAL_FK);
      }
      SQLTable keySqlTbl =
          stmt.innerJoin(
              mapExpr.getSQLTable(),
              mapExpr.getSQLTable().getTable().getIdMapping(),
              keyTbl,
              null,
              ownerMapping,
              null,
              null);

      if (valCmd != null) {
        DatastoreClass valTbl = storeMgr.getDatastoreClass(valCmd.getFullClassName(), clr);
        SQLTable valSqlTbl =
            stmt.innerJoin(
                keySqlTbl,
                keyTbl.getMemberMapping(keyValMmd),
                valTbl,
                valAlias,
                valTbl.getIdMapping(),
                null,
                null);

        if (valIsUnbound) {
          // Bind the variable in the QueryGenerator
          valExpr = exprFactory.newExpression(stmt, valSqlTbl, valTbl.getIdMapping());
          stmt.getQueryGenerator()
              .bindVariable(varName, valCmd, valExpr.getSQLTable(), valExpr.getJavaTypeMapping());
        } else {
          // Add restrict to value
          SQLExpression valIdExpr =
              exprFactory.newExpression(stmt, valSqlTbl, valTbl.getIdMapping());
          stmt.whereAnd(valIdExpr.eq(valExpr), true);
        }
      } else {
        if (valIsUnbound) {
          // Bind the variable in the QueryGenerator
          valExpr = exprFactory.newExpression(stmt, keySqlTbl, keyTbl.getMemberMapping(keyValMmd));
          stmt.getQueryGenerator()
              .bindVariable(varName, null, valExpr.getSQLTable(), valExpr.getJavaTypeMapping());
        } else {
          // Add restrict to value
          SQLExpression valIdExpr =
              exprFactory.newExpression(stmt, keySqlTbl, keyTbl.getMemberMapping(keyValMmd));
          stmt.whereAnd(valIdExpr.eq(valExpr), true);
        }
      }
    }

    JavaTypeMapping m = exprFactory.getMappingForType(boolean.class, true);
    return exprFactory.newLiteral(stmt, m, true).eq(exprFactory.newLiteral(stmt, m, true));
  }