/**
   * Constructor, taking the meta data for the field, and the table it is mapped to.
   *
   * @param mmd MetaData for the field.
   * @param table Table definition
   */
  public RDBMSStoreData(AbstractMemberMetaData mmd, Table table) {
    super(mmd.getFullFieldName(), mmd, SCO_TYPE, null);

    if (table == null) {
      throw new NullPointerException("table should not be null");
    }
    this.table = table;
    this.tableName = table.toString();
    this.tableOwner = true;
    this.tableIdentifier = table.getIdentifier();

    String interfaceName =
        (table.getStoreManager().getMetaDataManager().isPersistentInterface(mmd.getType().getName())
            ? mmd.getType().getName()
            : null);
    if (interfaceName != null) {
      this.interfaceName = interfaceName;
    }
  }
  /**
   * Constructor for FCO data.
   *
   * @param cmd MetaData for the class.
   * @param table Table where the class is stored.
   * @param tableOwner Whether the class is the owner of the table.
   */
  public RDBMSStoreData(ClassMetaData cmd, Table table, boolean tableOwner) {
    super(cmd.getFullClassName(), cmd, FCO_TYPE, null);

    this.tableOwner = tableOwner;
    if (table != null) {
      this.table = table;
      this.tableName = table.toString();
      this.tableIdentifier = table.getIdentifier();
    }

    String interfaces = null;
    ImplementsMetaData[] implMds = cmd.getImplementsMetaData();
    if (implMds != null) {
      for (int i = 0; i < cmd.getImplementsMetaData().length; i++) {
        if (interfaces == null) {
          interfaces = "";
        } else {
          interfaces += ",";
        }
        interfaces += cmd.getImplementsMetaData()[i].getName();
      }
      this.interfaceName = interfaces;
    }
  }
  /**
   * Generate statement for clearing the (join table) container.
   *
   * <PRE>
   * DELETE FROM CONTAINERTABLE WHERE OWNERCOL = ? [AND RELATION_DISCRIM=?]
   * </PRE>
   *
   * TODO Add a discriminator restriction on this statement so we only clear ones with a valid
   * discriminator value
   *
   * @return Statement for clearing the container.
   */
  protected String getClearStmt() {
    if (clearStmt == null) {
      synchronized (this) {
        StringBuilder stmt =
            new StringBuilder("DELETE FROM ").append(containerTable.toString()).append(" WHERE ");
        BackingStoreHelper.appendWhereClauseForMapping(stmt, ownerMapping, null, true);
        if (getRelationDiscriminatorMapping() != null) {
          BackingStoreHelper.appendWhereClauseForMapping(
              stmt, relationDiscriminatorMapping, null, false);
        }

        clearStmt = stmt.toString();
      }
    }

    return clearStmt;
  }
  /**
   * Generate statement for getting the size of the container. The order part is only present when
   * an order mapping is used. The discriminator part is only present when the element has a
   * discriminator.
   *
   * <PRE>
   * SELECT COUNT(*) FROM TBL THIS
   * [INNER JOIN ELEM_TBL ELEM ON TBL.COL = ELEM.ID] - when no null
   * [LEFT OUTER JOIN ELEM_TBL ELEM ON TBL.COL = ELEM.ID] - when allows null
   * WHERE THIS.OWNERCOL=?
   * [AND THIS.ORDERCOL IS NOT NULL]
   * [AND (DISCRIMINATOR=? OR DISCRMINATOR=? OR DISCRIMINATOR=? [OR DISCRIMINATOR IS NULL])]
   * [AND RELATION_DISCRIM=?]
   * </PRE>
   *
   * The discriminator part includes all subclasses of the element type. If the element is in a
   * different table to the container then an INNER JOIN will be present to link the two tables, and
   * table aliases will be present also.
   *
   * @return The Statement returning the size of the container.
   */
  protected String getSizeStmt() {
    if (sizeStmt != null) {
      // Statement exists and didn't need any discriminator when setting up the statement so just
      // reuse it
      return sizeStmt;
    }

    synchronized (this) {
      boolean usingDiscriminatorInSizeStmt = false;
      String containerAlias = "THIS";
      StringBuilder stmt = new StringBuilder();
      if (elementInfo == null) {
        // Serialised/embedded elements in a join table
        stmt.append("SELECT COUNT(*) FROM ")
            .append(containerTable.toString())
            .append(" ")
            .append(containerAlias);
        stmt.append(" WHERE ");
        BackingStoreHelper.appendWhereClauseForMapping(stmt, ownerMapping, containerAlias, true);
        if (orderMapping != null) {
          // If an ordering is present, restrict to items where the index is not null to
          // eliminate records that are added but may not be positioned yet.
          for (int i = 0; i < orderMapping.getNumberOfDatastoreMappings(); i++) {
            stmt.append(" AND ");
            stmt.append(containerAlias)
                .append(".")
                .append(orderMapping.getDatastoreMapping(i).getColumn().getIdentifier().toString());
            stmt.append(">=0");
          }
        }
        if (relationDiscriminatorMapping != null) {
          BackingStoreHelper.appendWhereClauseForMapping(
              stmt, relationDiscriminatorMapping, containerAlias, false);
        }
        sizeStmt = stmt.toString();
        return sizeStmt;
      }

      if (usingJoinTable()) {
        // Join table collection/array, so do COUNT of join table
        String joinedElementAlias = "ELEM";
        ElementInfo elemInfo = elementInfo[0];

        stmt.append("SELECT COUNT(*) FROM ")
            .append(containerTable.toString())
            .append(" ")
            .append(containerAlias);

        // Add join to element table if required (only allows for 1 element table currently)
        boolean joinedDiscrim = false;
        if (elemInfo.getDiscriminatorMapping() != null) {
          // Need join to the element table to restrict the discriminator
          joinedDiscrim = true;
          JavaTypeMapping elemIdMapping = elemInfo.getDatastoreClass().getIdMapping();
          stmt.append(allowNulls ? " LEFT OUTER JOIN " : " INNER JOIN ");
          stmt.append(elemInfo.getDatastoreClass().toString())
              .append(" ")
              .append(joinedElementAlias)
              .append(" ON ");
          for (int j = 0; j < elementMapping.getNumberOfDatastoreMappings(); j++) {
            if (j > 0) {
              stmt.append(" AND ");
            }
            stmt.append(containerAlias)
                .append(".")
                .append(elementMapping.getDatastoreMapping(j).getColumn().getIdentifier());
            stmt.append("=");
            stmt.append(joinedElementAlias)
                .append(".")
                .append(elemIdMapping.getDatastoreMapping(j).getColumn().getIdentifier());
          }
        }
        // TODO Add join to owner if ownerMapping is for supertable

        stmt.append(" WHERE ");
        BackingStoreHelper.appendWhereClauseForMapping(stmt, ownerMapping, containerAlias, true);
        if (orderMapping != null) {
          // If an ordering is present, restrict to items where the index is not null to
          // eliminate records that are added but may not be positioned yet.
          for (int j = 0; j < orderMapping.getNumberOfDatastoreMappings(); j++) {
            stmt.append(" AND ");
            stmt.append(containerAlias)
                .append(".")
                .append(orderMapping.getDatastoreMapping(j).getColumn().getIdentifier().toString());
            stmt.append(">=0");
          }
        }

        // Add a discriminator filter for collections with an element discriminator
        StringBuilder discrStmt = new StringBuilder();
        if (elemInfo.getDiscriminatorMapping() != null) {
          usingDiscriminatorInSizeStmt = true;
          JavaTypeMapping discrimMapping = elemInfo.getDiscriminatorMapping();

          Collection<String> classNames =
              storeMgr.getSubClassesForClass(elemInfo.getClassName(), true, clr);
          classNames.add(elemInfo.getClassName());
          for (String className : classNames) {
            Class cls = clr.classForName(className);
            if (!Modifier.isAbstract(cls.getModifiers())) {
              for (int j = 0; j < discrimMapping.getNumberOfDatastoreMappings(); j++) {
                if (discrStmt.length() > 0) {
                  discrStmt.append(" OR ");
                }

                discrStmt.append(joinedDiscrim ? joinedElementAlias : containerAlias);
                discrStmt.append(".");
                discrStmt.append(
                    discrimMapping.getDatastoreMapping(j).getColumn().getIdentifier().toString());
                discrStmt.append("=");
                discrStmt.append(
                    ((AbstractDatastoreMapping) discrimMapping.getDatastoreMapping(j))
                        .getUpdateInputParameter());
              }
            }
          }
        }

        if (discrStmt.length() > 0) {
          stmt.append(" AND (");
          stmt.append(discrStmt);
          if (allowNulls) {
            stmt.append(" OR ");
            stmt.append(
                elemInfo
                    .getDiscriminatorMapping()
                    .getDatastoreMapping(0)
                    .getColumn()
                    .getIdentifier()
                    .toString());
            stmt.append(" IS NULL");
          }
          stmt.append(")");
        }
        if (relationDiscriminatorMapping != null) {
          BackingStoreHelper.appendWhereClauseForMapping(
              stmt, relationDiscriminatorMapping, containerAlias, false);
        }
      } else {
        // ForeignKey collection/array, so UNION all of the element COUNTs
        for (int i = 0; i < elementInfo.length; i++) {
          if (i > 0) {
            stmt.append(" UNION ");
          }
          ElementInfo elemInfo = elementInfo[i];

          stmt.append("SELECT COUNT(*),")
              .append("'" + elemInfo.getAbstractClassMetaData().getName() + "'");
          stmt.append(" FROM ")
              .append(elemInfo.getDatastoreClass().toString())
              .append(" ")
              .append(containerAlias);

          stmt.append(" WHERE ");
          BackingStoreHelper.appendWhereClauseForMapping(stmt, ownerMapping, containerAlias, true);
          if (orderMapping != null) {
            // If an ordering is present, restrict to items where the index is not null to
            // eliminate records that are added but may not be positioned yet.
            for (int j = 0; j < orderMapping.getNumberOfDatastoreMappings(); j++) {
              stmt.append(" AND ");
              stmt.append(containerAlias)
                  .append(".")
                  .append(
                      orderMapping.getDatastoreMapping(j).getColumn().getIdentifier().toString());
              stmt.append(">=0");
            }
          }

          // Add a discriminator filter for collections with an element discriminator
          StringBuilder discrStmt = new StringBuilder();
          if (elemInfo.getDiscriminatorMapping() != null) {
            usingDiscriminatorInSizeStmt = true;
            JavaTypeMapping discrimMapping = elemInfo.getDiscriminatorMapping();

            Collection<String> classNames =
                storeMgr.getSubClassesForClass(elemInfo.getClassName(), true, clr);
            classNames.add(elemInfo.getClassName());
            for (String className : classNames) {
              Class cls = clr.classForName(className);
              if (!Modifier.isAbstract(cls.getModifiers())) {
                for (int j = 0; j < discrimMapping.getNumberOfDatastoreMappings(); j++) {
                  if (discrStmt.length() > 0) {
                    discrStmt.append(" OR ");
                  }

                  discrStmt
                      .append(containerAlias)
                      .append(".")
                      .append(
                          discrimMapping
                              .getDatastoreMapping(j)
                              .getColumn()
                              .getIdentifier()
                              .toString());
                  discrStmt.append("=");
                  discrStmt.append(
                      ((AbstractDatastoreMapping) discrimMapping.getDatastoreMapping(j))
                          .getUpdateInputParameter());
                }
              }
            }
          }

          if (discrStmt.length() > 0) {
            stmt.append(" AND (");
            stmt.append(discrStmt);
            if (allowNulls) {
              stmt.append(" OR ");
              stmt.append(
                  elemInfo
                      .getDiscriminatorMapping()
                      .getDatastoreMapping(0)
                      .getColumn()
                      .getIdentifier()
                      .toString());
              stmt.append(" IS NULL");
            }
            stmt.append(")");
          }
          if (relationDiscriminatorMapping != null) {
            BackingStoreHelper.appendWhereClauseForMapping(
                stmt, relationDiscriminatorMapping, containerAlias, false);
          }
        }
      }

      if (!usingDiscriminatorInSizeStmt) {
        sizeStmt = stmt.toString();
      }
      return stmt.toString();
    }
  }
  /**
   * Generates the statement for adding items to a (join table) container. The EMBEDDEDFIELDX
   * columns are only added for embedded PC elements.
   *
   * <PRE>
   * INSERT INTO COLLTABLE (OWNERCOL,[ELEMENTCOL],[EMBEDDEDFIELD1, EMBEDDEDFIELD2,...],[ORDERCOL]) VALUES (?,?,?)
   * </PRE>
   *
   * @return The Statement for adding an item
   */
  protected String getAddStmtForJoinTable() {
    if (addStmt == null) {
      synchronized (this) {
        StringBuilder stmt = new StringBuilder("INSERT INTO ");
        stmt.append(containerTable.toString());
        stmt.append(" (");
        for (int i = 0; i < getOwnerMapping().getNumberOfDatastoreMappings(); i++) {
          if (i > 0) {
            stmt.append(",");
          }
          stmt.append(
              getOwnerMapping().getDatastoreMapping(i).getColumn().getIdentifier().toString());
        }

        for (int i = 0; i < elementMapping.getNumberOfDatastoreMappings(); i++) {
          stmt.append(",")
              .append(elementMapping.getDatastoreMapping(i).getColumn().getIdentifier().toString());
        }

        if (orderMapping != null) {
          for (int i = 0; i < orderMapping.getNumberOfDatastoreMappings(); i++) {
            stmt.append(",")
                .append(orderMapping.getDatastoreMapping(i).getColumn().getIdentifier().toString());
          }
        }
        if (relationDiscriminatorMapping != null) {
          for (int i = 0; i < relationDiscriminatorMapping.getNumberOfDatastoreMappings(); i++) {
            stmt.append(",")
                .append(
                    relationDiscriminatorMapping
                        .getDatastoreMapping(i)
                        .getColumn()
                        .getIdentifier()
                        .toString());
          }
        }

        stmt.append(") VALUES (");
        for (int i = 0; i < getOwnerMapping().getNumberOfDatastoreMappings(); i++) {
          if (i > 0) {
            stmt.append(",");
          }
          stmt.append(
              ((AbstractDatastoreMapping) getOwnerMapping().getDatastoreMapping(i))
                  .getInsertionInputParameter());
        }

        for (int i = 0; i < elementMapping.getNumberOfDatastoreMappings(); i++) {
          stmt.append(",")
              .append(
                  ((AbstractDatastoreMapping) elementMapping.getDatastoreMapping(0))
                      .getInsertionInputParameter());
        }

        if (orderMapping != null) {
          for (int i = 0; i < orderMapping.getNumberOfDatastoreMappings(); i++) {
            stmt.append(",")
                .append(
                    ((AbstractDatastoreMapping) orderMapping.getDatastoreMapping(0))
                        .getInsertionInputParameter());
          }
        }
        if (relationDiscriminatorMapping != null) {
          for (int i = 0; i < relationDiscriminatorMapping.getNumberOfDatastoreMappings(); i++) {
            stmt.append(",")
                .append(
                    ((AbstractDatastoreMapping) relationDiscriminatorMapping.getDatastoreMapping(0))
                        .getInsertionInputParameter());
          }
        }

        stmt.append(") ");

        addStmt = stmt.toString();
      }
    }

    return addStmt;
  }
 /**
  * Method to return the INSERT statement to use when inserting into a table that has no columns
  * specified. This is the case when we have a single column in the table and that column is
  * autoincrement/identity (and so is assigned automatically in the datastore).
  *
  * @param table The table
  * @return The statement for the INSERT
  */
 public String getInsertStatementForNoColumns(Table table) {
   return "INSERT INTO " + table.toString() + " DEFAULT VALUES";
 }
 public String getDropTableStatement(Table table) {
   return "DROP TABLE " + table.toString();
 }