/**
   * Utility that does a discriminator candidate query for the specified candidate and subclasses
   * and returns the class name of the instance that has the specified identity (if any).
   *
   * @param storeMgr RDBMS StoreManager
   * @param ec execution context
   * @param id The id
   * @param cmd Metadata for the root candidate class
   * @return Name of the class with this identity (or null if none found)
   */
  public static String getClassNameForIdUsingDiscriminator(
      RDBMSStoreManager storeMgr, ExecutionContext ec, Object id, AbstractClassMetaData cmd) {
    // Check for input error
    if (cmd == null || id == null) {
      return null;
    }

    SQLExpressionFactory exprFactory = storeMgr.getSQLExpressionFactory();
    ClassLoaderResolver clr = ec.getClassLoaderResolver();
    DatastoreClass primaryTable = storeMgr.getDatastoreClass(cmd.getFullClassName(), clr);

    // Form the query to find which one of these classes has the instance with this id
    DiscriminatorStatementGenerator stmtGen =
        new DiscriminatorStatementGenerator(
            storeMgr, clr, clr.classForName(cmd.getFullClassName()), true, null, null);
    stmtGen.setOption(SelectStatementGenerator.OPTION_RESTRICT_DISCRIM);
    SelectStatement sqlStmt = stmtGen.getStatement();

    // Select the discriminator
    JavaTypeMapping discrimMapping = primaryTable.getDiscriminatorMapping(true);
    SQLTable discrimSqlTbl =
        SQLStatementHelper.getSQLTableForMappingOfTable(
            sqlStmt, sqlStmt.getPrimaryTable(), discrimMapping);
    sqlStmt.select(discrimSqlTbl, discrimMapping, null);

    // Restrict to this id
    JavaTypeMapping idMapping = primaryTable.getIdMapping();
    JavaTypeMapping idParamMapping = new PersistableIdMapping((PersistableMapping) idMapping);
    SQLExpression sqlFldExpr =
        exprFactory.newExpression(sqlStmt, sqlStmt.getPrimaryTable(), idMapping);
    SQLExpression sqlFldVal = exprFactory.newLiteralParameter(sqlStmt, idParamMapping, id, "ID");
    sqlStmt.whereAnd(sqlFldExpr.eq(sqlFldVal), true);

    // Perform the query
    try {
      ManagedConnection mconn = storeMgr.getConnection(ec);
      SQLController sqlControl = storeMgr.getSQLController();
      if (ec.getSerializeReadForClass(cmd.getFullClassName())) {
        sqlStmt.addExtension(SQLStatement.EXTENSION_LOCK_FOR_UPDATE, true);
      }

      try {
        PreparedStatement ps =
            SQLStatementHelper.getPreparedStatementForSQLStatement(sqlStmt, ec, mconn, null, null);
        String statement = sqlStmt.getSQLText().toSQL();
        try {
          ResultSet rs = sqlControl.executeStatementQuery(ec, mconn, statement, ps);
          try {
            if (rs != null) {
              while (rs.next()) {
                DiscriminatorMetaData dismd = discrimMapping.getTable().getDiscriminatorMetaData();
                return RDBMSQueryUtils.getClassNameFromDiscriminatorResultSetRow(
                    discrimMapping, dismd, rs, ec);
              }
            }
          } finally {
            rs.close();
          }
        } finally {
          sqlControl.closeStatement(mconn, ps);
        }
      } finally {
        mconn.release();
      }
    } catch (SQLException sqe) {
      NucleusLogger.DATASTORE.error("Exception thrown on querying of discriminator for id", sqe);
      throw new NucleusDataStoreException(sqe.toString(), sqe);
    }

    return null;
  }
  /**
   * Utility that does a union candidate query for the specified candidate(s) and subclasses and
   * returns the class name of the instance that has the specified identity (if any).
   *
   * @param storeMgr RDBMS StoreManager
   * @param ec execution context
   * @param id The id
   * @param rootCmds Metadata for the classes at the root
   * @return Name of the class with this identity (or null if none found)
   */
  public static String getClassNameForIdUsingUnion(
      RDBMSStoreManager storeMgr,
      ExecutionContext ec,
      Object id,
      List<AbstractClassMetaData> rootCmds) {
    // Check for input error
    if (rootCmds == null || rootCmds.isEmpty() || id == null) {
      return null;
    }

    SQLExpressionFactory exprFactory = storeMgr.getSQLExpressionFactory();
    ClassLoaderResolver clr = ec.getClassLoaderResolver();

    // Form a query UNIONing all possible root candidates (and their subclasses)
    Iterator<AbstractClassMetaData> rootCmdIter = rootCmds.iterator();

    AbstractClassMetaData sampleCmd =
        null; // Metadata for sample class in the tree so we can check if needs locking
    SelectStatement sqlStmtMain = null;
    while (rootCmdIter.hasNext()) {
      AbstractClassMetaData rootCmd = rootCmdIter.next();
      DatastoreClass rootTbl = storeMgr.getDatastoreClass(rootCmd.getFullClassName(), clr);
      if (rootTbl == null) {
        // Class must be using "subclass-table" (no table of its own) so find where it is
        AbstractClassMetaData[] subcmds = storeMgr.getClassesManagingTableForClass(rootCmd, clr);
        if (subcmds == null || subcmds.length == 0) {
          // No table for this class so ignore
        } else {
          for (int i = 0; i < subcmds.length; i++) {
            UnionStatementGenerator stmtGen =
                new UnionStatementGenerator(
                    storeMgr,
                    clr,
                    clr.classForName(subcmds[i].getFullClassName()),
                    true,
                    null,
                    null);
            stmtGen.setOption(SelectStatementGenerator.OPTION_SELECT_NUCLEUS_TYPE);
            if (sqlStmtMain == null) {
              sampleCmd = subcmds[i];
              sqlStmtMain = stmtGen.getStatement();

              // WHERE (object id) = ?
              JavaTypeMapping idMapping = sqlStmtMain.getPrimaryTable().getTable().getIdMapping();
              JavaTypeMapping idParamMapping =
                  new PersistableIdMapping((PersistableMapping) idMapping);
              SQLExpression fieldExpr =
                  exprFactory.newExpression(sqlStmtMain, sqlStmtMain.getPrimaryTable(), idMapping);
              SQLExpression fieldVal =
                  exprFactory.newLiteralParameter(sqlStmtMain, idParamMapping, id, "ID");
              sqlStmtMain.whereAnd(fieldExpr.eq(fieldVal), true);
            } else {
              SelectStatement sqlStmt = stmtGen.getStatement();

              // WHERE (object id) = ?
              JavaTypeMapping idMapping = sqlStmt.getPrimaryTable().getTable().getIdMapping();
              JavaTypeMapping idParamMapping =
                  new PersistableIdMapping((PersistableMapping) idMapping);
              SQLExpression fieldExpr =
                  exprFactory.newExpression(sqlStmt, sqlStmt.getPrimaryTable(), idMapping);
              SQLExpression fieldVal =
                  exprFactory.newLiteralParameter(sqlStmt, idParamMapping, id, "ID");
              sqlStmt.whereAnd(fieldExpr.eq(fieldVal), true);

              sqlStmtMain.union(sqlStmt);
            }
          }
        }
      } else {
        UnionStatementGenerator stmtGen =
            new UnionStatementGenerator(
                storeMgr, clr, clr.classForName(rootCmd.getFullClassName()), true, null, null);
        stmtGen.setOption(SelectStatementGenerator.OPTION_SELECT_NUCLEUS_TYPE);
        if (sqlStmtMain == null) {
          sampleCmd = rootCmd;
          sqlStmtMain = stmtGen.getStatement();

          // WHERE (object id) = ?
          JavaTypeMapping idMapping = sqlStmtMain.getPrimaryTable().getTable().getIdMapping();
          JavaTypeMapping idParamMapping = new PersistableIdMapping((PersistableMapping) idMapping);
          SQLExpression fieldExpr =
              exprFactory.newExpression(sqlStmtMain, sqlStmtMain.getPrimaryTable(), idMapping);
          SQLExpression fieldVal =
              exprFactory.newLiteralParameter(sqlStmtMain, idParamMapping, id, "ID");
          sqlStmtMain.whereAnd(fieldExpr.eq(fieldVal), true);
        } else {
          SelectStatement sqlStmt = stmtGen.getStatement();

          // WHERE (object id) = ?
          JavaTypeMapping idMapping = sqlStmt.getPrimaryTable().getTable().getIdMapping();
          JavaTypeMapping idParamMapping = new PersistableIdMapping((PersistableMapping) idMapping);
          SQLExpression fieldExpr =
              exprFactory.newExpression(sqlStmt, sqlStmt.getPrimaryTable(), idMapping);
          SQLExpression fieldVal =
              exprFactory.newLiteralParameter(sqlStmt, idParamMapping, id, "ID");
          sqlStmt.whereAnd(fieldExpr.eq(fieldVal), true);

          sqlStmtMain.union(sqlStmt);
        }
      }
    }

    // Perform the query
    try {
      ManagedConnection mconn = storeMgr.getConnection(ec);
      SQLController sqlControl = storeMgr.getSQLController();
      if (ec.getSerializeReadForClass(sampleCmd.getFullClassName())) {
        sqlStmtMain.addExtension(SQLStatement.EXTENSION_LOCK_FOR_UPDATE, true);
      }

      try {
        PreparedStatement ps =
            SQLStatementHelper.getPreparedStatementForSQLStatement(
                sqlStmtMain, ec, mconn, null, null);
        String statement = sqlStmtMain.getSQLText().toSQL();
        try {
          ResultSet rs = sqlControl.executeStatementQuery(ec, mconn, statement, ps);
          try {
            if (rs != null) {
              while (rs.next()) {
                try {
                  return rs.getString(UnionStatementGenerator.NUC_TYPE_COLUMN).trim();
                } catch (SQLException sqle) {
                }
              }
            }
          } finally {
            rs.close();
          }
        } finally {
          sqlControl.closeStatement(mconn, ps);
        }
      } finally {
        mconn.release();
      }
    } catch (SQLException sqe) {
      NucleusLogger.DATASTORE.error("Exception with UNION statement", sqe);
      throw new NucleusDataStoreException(sqe.toString());
    }

    return null;
  }