Package org.jpox.store.rdbms.table

Source Code of org.jpox.store.rdbms.table.TableImpl

/**********************************************************************
Copyright (c) 2002 Mike Martin and others. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Contributors:
2003 Erik Bengtson  - removed unused variable
2003 Andy Jefferson - localised
2003 Andy Jefferson - addition of checkExists, made getExpectedPK protected
2004 Andy Jefferson - rewritten to remove many levels of inheritance
2004 Andy Jefferson - added validation of columns, and auto create option
2004 Andy Jefferson - separated out validatePK
    ...
**********************************************************************/
package org.jpox.store.rdbms.table;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.jpox.ClassLoaderResolver;
import org.jpox.metadata.FieldRole;
import org.jpox.store.exceptions.NoTableManagedException;
import org.jpox.store.mapped.DatastoreClass;
import org.jpox.store.mapped.DatastoreField;
import org.jpox.store.mapped.DatastoreIdentifier;
import org.jpox.store.mapped.IdentifierFactory;
import org.jpox.store.rdbms.Column;
import org.jpox.store.rdbms.RDBMSManager;
import org.jpox.store.rdbms.RDBMSStoreHelper;
import org.jpox.store.rdbms.SQLWarnings;
import org.jpox.store.rdbms.columninfo.ColumnInfo;
import org.jpox.store.rdbms.exceptions.MissingColumnException;
import org.jpox.store.rdbms.exceptions.MissingTableException;
import org.jpox.store.rdbms.exceptions.NotATableException;
import org.jpox.store.rdbms.exceptions.UnexpectedColumnException;
import org.jpox.store.rdbms.exceptions.WrongPrimaryKeyException;
import org.jpox.store.rdbms.key.CandidateKey;
import org.jpox.store.rdbms.key.ForeignKey;
import org.jpox.store.rdbms.key.Index;
import org.jpox.store.rdbms.key.PrimaryKey;
import org.jpox.store.rdbms.sqlidentifier.RDBMSIdentifierFactory;
import org.jpox.store.rdbms.typeinfo.ForeignKeyInfo;
import org.jpox.util.JPOXLogger;
import org.jpox.util.StringUtils;

/**
* Class representing a table in a datastore (RDBMS).
* Provides a series of methods for validating the aspects of the table, namely
* <UL>
* <LI>validate - Validate the table</LI>
* <LI>validateColumns - Validate the columns in the table</LI>
* <LI>validatePrimaryKey - Validate the primary key</LI>
* <LI>validateIndices - Validate the indices on the table</LI>
* <LI>validateForeignKeys - Validate all FKs for the table</LI>
* <LI>validateConstraints - Validate the indices and FKs.</LI>
* </UL>
*
* @version $Revision: 1.76 $
*/
public abstract class TableImpl extends AbstractTable
{
    /**
     * Constructor.
     * @param name The name of the table (in SQL).
     * @param storeMgr The StoreManager for this table.
     */
    public TableImpl(DatastoreIdentifier name, RDBMSManager storeMgr)
    {
        super(name, storeMgr);
    }

    /**
     * Pre initilize. For things that must be initialized right after constructor
     * @param clr the ClassLoaderResolver
     */
    public void preInitialize(final ClassLoaderResolver clr)
    {
        assertIsUninitialized();
        //nothing to do here
    }   
   
   
    /**
     * Post initilize. For things that must be set after all classes have been initialized before
     * @param clr the ClassLoaderResolver
     */
    public void postInitialize(final ClassLoaderResolver clr)
    {
        assertIsInitialized();
        //nothing to do here
    }   

    /**
     * Accessor for the primary key for this table.
     * Will always return a PrimaryKey but if we have defined no columns,
     * the pk.size() will be 0.
     * @return The primary key.
     */
    public PrimaryKey getPrimaryKey()
    {
        PrimaryKey pk = new PrimaryKey(this);
        Iterator i = columns.iterator();
        while (i.hasNext())
        {
            Column col = (Column) i.next();
            if (col.isPrimaryKey())
            {
                pk.addDatastoreField(col);
            }
        }

        return pk;
    }

    /**
     * Method to validate the table in the datastore.
     * @param conn The JDBC Connection
     * @param validateColumnStructure Whether to validate the column structure, or just the column existence
     * @param autoCreate Whether to update the table to fix any validation errors. Only applies to missing columns.
     * @param autoCreateErrors Exceptions found in the "auto-create" process
     * @return Whether the database was modified
     * @throws SQLException Thrown when an error occurs in the JDBC calls
     */
    public boolean validate(Connection conn, boolean validateColumnStructure, boolean autoCreate, Collection autoCreateErrors)
    throws SQLException
    {
        assertIsInitialized();

        // Check existence
        int tableType = RDBMSStoreHelper.getTableType(storeMgr, this, conn);
        if (tableType == TABLE_TYPE_MISSING)
        {
            throw new MissingTableException(getCatalogName(), getSchemaName(), this.toString());
        }

        long startTime = System.currentTimeMillis();
        if (JPOXLogger.DATASTORE_SCHEMA.isDebugEnabled())
        {
            JPOXLogger.DATASTORE_SCHEMA.debug(LOCALISER.msg("057032", this));
        }

        // Check type
        if (tableType != TABLE_TYPE_TABLE)
        {
            throw new NotATableException(this.toString(),tableType);
        }

        // Validate the column(s)
        validateColumns(conn, validateColumnStructure, autoCreate, autoCreateErrors);

        // Validate the primary key(s)
        try
        {
            validatePrimaryKey(conn);
        }
        catch (WrongPrimaryKeyException wpke)
        {
            if (autoCreateErrors != null)
            {
                autoCreateErrors.add(wpke);
            }
            else
            {
                throw wpke;
            }
        }

        state = TABLE_STATE_VALIDATED;
        if (JPOXLogger.DATASTORE_SCHEMA.isDebugEnabled())
        {
            JPOXLogger.DATASTORE_SCHEMA.debug(LOCALISER.msg("045000", (System.currentTimeMillis() - startTime)));
        }

        return false;
    }

    /**
     * Utility to validate the columns of the table.
     * Will throw a MissingColumnException if a column is not found (and
     * is not required to auto create it)
     * @param conn Connection to use for validation
     * @param validateColumnStructure Whether to validate down to the structure of the columns, or just their existence
     * @param autoCreate Whether to auto create any missing columns
     * @param autoCreateErrors Exceptions found in the "auto-create" process
     * @return Whether it validates
     * @throws SQLException Thrown if an error occurs in the validation process
     */
    public boolean validateColumns(Connection conn, boolean validateColumnStructure, boolean autoCreate, Collection autoCreateErrors)
    throws SQLException
    {
        HashMap unvalidated = new HashMap(columnsByName);
        Iterator i = storeMgr.getColumnInfoForTable(this, conn).iterator();
        while (i.hasNext())
        {
            ColumnInfo ci = (ColumnInfo) i.next();

            // Create an identifier to use for the real column - use "CUSTOM" because we dont want truncation
            DatastoreIdentifier colName = storeMgr.getIdentifierFactory().newDatastoreFieldIdentifier(ci.getColumnName(),
                this.storeMgr.getOMFContext().getTypeManager().isDefaultEmbeddedType(String.class), FieldRole.ROLE_CUSTOM);
            Column col = (Column) unvalidated.get(colName);
            if (col != null)
            {
                if (validateColumnStructure)
                {
                    col.initializeColumnInfoFromDatastore(ci);
                    col.validate(ci);
                    unvalidated.remove(colName);
                }
                else
                {
                    unvalidated.remove(colName);
                }
            }
        }

        if (unvalidated.size() > 0)
        {
            if (autoCreate)
            {
                // Add all missing columns
                List stmts = new ArrayList();
                Set columnKeys = unvalidated.keySet();
                Iterator columnKeysIter = columnKeys.iterator();
                while (columnKeysIter.hasNext())
                {
                    DatastoreIdentifier colName = (DatastoreIdentifier)columnKeysIter.next();

                    Column col = (Column)(unvalidated.get(colName));
                    String addColStmt = dba.getAddColumnStatement(this, col);
                    stmts.add(addColStmt);
                    JPOXLogger.DATASTORE_SCHEMA.info(LOCALISER.msg("057031", col.getIdentifier(), this.toString()));
                }

                try
                {
                    executeDdlStatementList(stmts, conn);
                }
                catch (SQLException sqle)
                {
                    if (autoCreateErrors != null)
                    {
                        autoCreateErrors.add(sqle);
                    }
                    else
                    {
                        throw sqle;
                    }
                }

                // Invalidate the cached information for this table since it now has a new column
                storeMgr.invalidateColumnInfoForTable(this);
            }
            else
            {
                MissingColumnException mce = new MissingColumnException(this, unvalidated.values());
                if (autoCreateErrors != null)
                {
                    autoCreateErrors.add(mce);
                }
                else
                {
                    throw mce;
                }
            }
        }
        state = TABLE_STATE_VALIDATED;

        return true;
    }

    /**
     * Utility to load the structure/metadata of primary key columns of the table.
     * @param conn Connection to use for validation
     * @throws SQLException Thrown if an error occurs in the initialization process
     */
    public void initializeColumnInfoForPrimaryKeyColumns(Connection conn)
    throws SQLException
    {
        Iterator i = columnsByName.values().iterator();
        while (i.hasNext())
        {
            Column col = (Column) i.next();
            if (col.isPrimaryKey())
            {
                ColumnInfo ci = storeMgr.getColumnInfoForColumnName(this, conn, col.getIdentifier());
                if (ci != null)
                {
                    col.initializeColumnInfoFromDatastore(ci);
                }
            }
        }
    }

    /**
     * Initialize the default value for columns if null using the values from the datastore.
     * @param conn The JDBC Connection
     * @throws SQLException Thrown if an error occurs in the default initialisation.
     */
    public void initializeColumnInfoFromDatastore(Connection conn)
    throws SQLException
    {
        HashMap columns = new HashMap(columnsByName);
        Iterator i = storeMgr.getColumnInfoForTable(this, conn).iterator();
        while (i.hasNext())
        {
            ColumnInfo ci = (ColumnInfo) i.next();
            DatastoreIdentifier colName = storeMgr.getIdentifierFactory().newIdentifier(IdentifierFactory.COLUMN, ci.getColumnName());
            Column col = (Column) columns.get(colName);
            if (col != null)
            {
                col.initializeColumnInfoFromDatastore(ci);
            }
        }
    }

    /**
     * Utility method to validate the primary key of the table.
     * Will throw a WrongPrimaryKeyException if the PK is incorrect.
     * TODO Add an auto_create parameter on this
     * @param conn Connection to use
     * @return Whether it validates
     * @throws SQLException When an error occurs in the valdiation
     */
    protected boolean validatePrimaryKey(Connection conn)
    throws SQLException
    {
        Map actualPKs = getExistingPrimaryKeys(conn);
        PrimaryKey expectedPK = getPrimaryKey();
        if (expectedPK.size() == 0)
        {
            if (!actualPKs.isEmpty())
            {
                throw new WrongPrimaryKeyException(this.toString(), expectedPK.toString(), StringUtils.collectionToString(actualPKs.values()));
            }
        }
        else
        {
            if (actualPKs.size() != 1 ||
                !actualPKs.values().contains(expectedPK))
            {
                throw new WrongPrimaryKeyException(this.toString(), expectedPK.toString(), StringUtils.collectionToString(actualPKs.values()));
            }
        }

        return true;
    }

    /**
     * Method to validate any constraints, and auto create them if required.
     * @param conn The JDBC Connection
     * @param autoCreate Whether to auto create the constraints if not existing
     * @param autoCreateErrors Errors found in the "auto-create" process
     * @param clr The ClassLoaderResolver
     * @return Whether the database was modified
     * @throws SQLException Thrown when an error occurs in the JDBC calls
     */
    public boolean validateConstraints(Connection conn, boolean autoCreate, Collection autoCreateErrors, ClassLoaderResolver clr)
    throws SQLException
    {
        assertIsInitialized();

        boolean cksWereModified;
        boolean fksWereModified;
        boolean idxsWereModified;
        if (dba.createIndexesBeforeForeignKeys())
        {
            idxsWereModified = validateIndices(conn, autoCreate, autoCreateErrors, clr);
            fksWereModified = validateForeignKeys(conn, autoCreate, autoCreateErrors, clr);
            cksWereModified = validateCandidateKeys(conn, autoCreate, autoCreateErrors);
        }
        else
        {
            cksWereModified = validateCandidateKeys(conn, autoCreate, autoCreateErrors);
            fksWereModified = validateForeignKeys(conn, autoCreate, autoCreateErrors, clr);
            idxsWereModified = validateIndices(conn, autoCreate, autoCreateErrors, clr);
        }

        return fksWereModified || idxsWereModified || cksWereModified;
    }

    /**
     * Method used to create all constraints for a brand new table.
     * @param conn The JDBC Connection
     * @param autoCreateErrors Errors found in the "auto-create" process
     * @param clr The ClassLoaderResolver
     * @return Whether the database was modified
     * @throws SQLException Thrown when an error occurs in the JDBC calls
     */
    public boolean createConstraints(Connection conn, Collection autoCreateErrors, ClassLoaderResolver clr)
    throws SQLException
    {
        assertIsInitialized();

        boolean cksWereModified;
        boolean fksWereModified;
        boolean idxsWereModified;
        if (dba.createIndexesBeforeForeignKeys())
        {
            idxsWereModified = createIndices(conn, autoCreateErrors, clr, Collections.EMPTY_MAP);
            fksWereModified = createForeignKeys(conn, autoCreateErrors, clr, Collections.EMPTY_MAP);
            cksWereModified = createCandidateKeys(conn, autoCreateErrors, Collections.EMPTY_MAP);
        }
        else
        {
            cksWereModified = createCandidateKeys(conn, autoCreateErrors, Collections.EMPTY_MAP);
            fksWereModified = createForeignKeys(conn, autoCreateErrors, clr, Collections.EMPTY_MAP);
            idxsWereModified = createIndices(conn, autoCreateErrors, clr, Collections.EMPTY_MAP);
        }

        return fksWereModified || idxsWereModified || cksWereModified;
    }

    /**
     * Method to validate any foreign keys on this table in the datastore, and
     * auto create any that are missing where required.
     *
     * @param conn The JDBC Connection
     * @param autoCreate Whether to auto create the FKs if not existing
     * @param autoCreateErrors Errors found during the auto-create process
     * @return Whether the database was modified
     * @throws SQLException Thrown when an error occurs in the JDBC calls
     */
    private boolean validateForeignKeys(Connection conn, boolean autoCreate, Collection autoCreateErrors, ClassLoaderResolver clr)
    throws SQLException
    {
        boolean dbWasModified = false;

        // Validate and/or create all foreign keys.
        Map actualForeignKeysByName = null;
        int numActualFKs = 0;
        if (isOutputtingDDL())
        {
            actualForeignKeysByName = new HashMap();
        }
        else
        {
            actualForeignKeysByName = getExistingForeignKeys(conn);
            numActualFKs = actualForeignKeysByName.size();
            if (JPOXLogger.DATASTORE_SCHEMA.isInfoEnabled())
            {
                JPOXLogger.DATASTORE_SCHEMA.info(LOCALISER.msg("058103", "" + numActualFKs, this));
            }
        }

        // Auto Create any missing foreign keys
        if (autoCreate)
        {
            dbWasModified = createForeignKeys(conn, autoCreateErrors, clr, actualForeignKeysByName);
        }
        else
        {
            Map stmtsByFKName = getSQLAddFKStatements(actualForeignKeysByName, clr);
            if (stmtsByFKName.isEmpty())
            {
                if (numActualFKs > 0)
                {
                    if (JPOXLogger.DATASTORE_SCHEMA.isInfoEnabled())
                    {
                        JPOXLogger.DATASTORE_SCHEMA.info(LOCALISER.msg("058104", "" + numActualFKs,this));
                    }
                }
            }
            else
            {
                // JPOX supports existing schemas so don't raise an exception.
                JPOXLogger.DATASTORE_SCHEMA.warn(LOCALISER.msg("058101",
                    this, stmtsByFKName.values()));
            }
        }
        return dbWasModified;
    }

    /**
     * Method to create any foreign keys on this table in the datastore
     *
     * @param conn The JDBC Connection
     * @param autoCreateErrors Errors found during the auto-create process
     * @param actualForeignKeysByName the current foreign keys
     * @return Whether the database was modified
     * @throws SQLException Thrown when an error occurs in the JDBC calls
     */
    private boolean createForeignKeys(Connection conn, Collection autoCreateErrors, ClassLoaderResolver clr, Map actualForeignKeysByName)
    throws SQLException
    {
        // Auto Create any missing foreign keys
        Map stmtsByFKName = getSQLAddFKStatements(actualForeignKeysByName, clr);
        Statement stmt = conn.createStatement();
        try
        {
            Iterator i = stmtsByFKName.entrySet().iterator();
            while (i.hasNext())
            {
                Map.Entry e = (Map.Entry) i.next();
                String fkName = (String) e.getKey();
                String stmtText = (String) e.getValue();
                if (JPOXLogger.DATASTORE_SCHEMA.isInfoEnabled())
                {
                    JPOXLogger.DATASTORE_SCHEMA.info(LOCALISER.msg("058100",
                        fkName, getCatalogName(), getSchemaName()));
                }

                try
                {
                    executeDdlStatement(stmt, stmtText);
                }
                catch (SQLException sqle)
                {
                    if (autoCreateErrors != null)
                    {
                        autoCreateErrors.add(sqle);
                    }
                    else
                    {
                        throw sqle;
                    }
                }
            }
        }
        finally
        {
            stmt.close();
        }
        return !stmtsByFKName.isEmpty();
    }
   
    /**
     * Method to validate any indices for this table, and auto create any
     * missing ones where required.
     * @param conn The JDBC Connection
     * @param autoCreate Whether to auto create any missing indices
     * @param autoCreateErrors Errors found during the auto-create process
     * @return Whether the database was changed
     * @throws SQLException Thrown when an error occurs in the JDBC calls
     */
    private boolean validateIndices(Connection conn, boolean autoCreate, Collection autoCreateErrors, ClassLoaderResolver clr)
    throws SQLException
    {
        boolean dbWasModified = false;

        // Retrieve the number of indexes in the datastore for this table.
        Map actualIndicesByName = null;
        int numActualIdxs = 0;
        if (isOutputtingDDL())
        {
            actualIndicesByName = new HashMap();
        }
        else
        {
            actualIndicesByName = getExistingIndices(conn);
            Iterator names = actualIndicesByName.keySet().iterator();
            while (names.hasNext())
            {
                DatastoreIdentifier idxName = (DatastoreIdentifier) names.next();
                Index idx = (Index)actualIndicesByName.get(idxName);
                if (idx.getDatastoreContainerObject().getIdentifier().toString().equals(identifier.toString()))
                {
                    // Table of the index is the same as this table so must be ours
                    ++numActualIdxs;
                }
            }
            if (JPOXLogger.DATASTORE_SCHEMA.isInfoEnabled())
            {
                JPOXLogger.DATASTORE_SCHEMA.info(LOCALISER.msg("058004", "" + numActualIdxs, this));
            }
        }

        // Auto Create any missing indices
        if (autoCreate)
        {
            dbWasModified = createIndices(conn, autoCreateErrors, clr, actualIndicesByName);
        }
        else
        {
            Map stmtsByIdxName = getSQLCreateIndexStatements(actualIndicesByName, clr);
            if (stmtsByIdxName.isEmpty())
            {
                if (numActualIdxs > 0)
                {
                    if (JPOXLogger.DATASTORE_SCHEMA.isInfoEnabled())
                    {
                        JPOXLogger.DATASTORE_SCHEMA.info(LOCALISER.msg("058005", "" + numActualIdxs, this));
                    }
                }
            }
            else
            {
                // JPOX supports existing schemas so don't raise an exception.
                JPOXLogger.DATASTORE_SCHEMA.warn(LOCALISER.msg("058003",
                    this, stmtsByIdxName.values()));
            }
        }
        return dbWasModified;
    }

    /**
     * Method to create any indices for this table
     * @param conn The JDBC Connection
     * @param autoCreateErrors Errors found during the auto-create process
     * @param actualIndicesByName the actual indices by name
     * @return Whether the database was changed
     * @throws SQLException Thrown when an error occurs in the JDBC calls
     */
    private boolean createIndices(Connection conn, Collection autoCreateErrors, ClassLoaderResolver clr, Map actualIndicesByName)
    throws SQLException
    {
        // Auto Create any missing indices
        Map stmtsByIdxName = getSQLCreateIndexStatements(actualIndicesByName, clr);
        Statement stmt = conn.createStatement();
        try
        {
            Iterator i = stmtsByIdxName.entrySet().iterator();
            while (i.hasNext())
            {
                Map.Entry e = (Map.Entry) i.next();
                String idxName = (String) e.getKey();
                String stmtText = (String) e.getValue();
                if (JPOXLogger.DATASTORE_SCHEMA.isInfoEnabled())
                {
                    JPOXLogger.DATASTORE_SCHEMA.info(LOCALISER.msg("058000",
                        idxName, getCatalogName(), getSchemaName()));
                }

                try
                {
                    executeDdlStatement(stmt, stmtText);
                }
                catch (SQLException sqle)
                {
                    if (autoCreateErrors != null)
                    {
                        autoCreateErrors.add(sqle);
                    }
                    else
                    {
                        throw sqle;
                    }
                }
            }
        }
        finally
        {
            stmt.close();
        }
        return !stmtsByIdxName.isEmpty();
    }
   
    /**
     * Method to validate any Candidate keys on this table in the datastore, and
     * auto create any that are missing where required.
     *
     * @param conn The JDBC Connection
     * @param autoCreate Whether to auto create the Candidate Keys if not existing
     * @param autoCreateErrors Errors found during the auto-create process
     * @return Whether the database was modified
     * @throws SQLException Thrown when an error occurs in the JDBC calls
     */
    private boolean validateCandidateKeys(Connection conn, boolean autoCreate, Collection autoCreateErrors)
    throws SQLException
    {
        boolean dbWasModified = false;

        // Validate and/or create all candidate keys.
        Map actualCandidateKeysByName = null;
        int numActualCKs = 0;
        if (isOutputtingDDL())
        {
            actualCandidateKeysByName = new HashMap();
        }
        else
        {
            actualCandidateKeysByName = getExistingCandidateKeys(conn);
            numActualCKs = actualCandidateKeysByName.size();
            if (JPOXLogger.DATASTORE_SCHEMA.isInfoEnabled())
            {
                JPOXLogger.DATASTORE_SCHEMA.info(LOCALISER.msg("058204", "" + numActualCKs, this));
            }
        }

        // Auto Create any missing candidate keys
        if (autoCreate)
        {
            dbWasModified = createCandidateKeys(conn, autoCreateErrors, actualCandidateKeysByName);
        }
        else
        {
            //validate only
            Map stmtsByCKName = getSQLAddCandidateKeyStatements(actualCandidateKeysByName);
            if (stmtsByCKName.isEmpty())
            {
                if (numActualCKs > 0)
                {
                    if (JPOXLogger.DATASTORE_SCHEMA.isInfoEnabled())
                    {
                        JPOXLogger.DATASTORE_SCHEMA.info(LOCALISER.msg("058205",
                            "" + numActualCKs,this));
                    }
                }
            }
            else
            {
                // JPOX supports existing schemas so don't raise an exception.
                JPOXLogger.DATASTORE_SCHEMA.warn(LOCALISER.msg("058201",
                    this, stmtsByCKName.values()));
            }
        }

        return dbWasModified;
    }

    /**
     * Method to create any Candidate keys on this table in the datastore, and
     * auto create any that are missing where required.
     *
     * @param conn The JDBC Connection
     * @param autoCreateErrors Errors found during the auto-create process
     * @return Whether the database was modified
     * @throws SQLException Thrown when an error occurs in the JDBC calls
     */
    private boolean createCandidateKeys(Connection conn, Collection autoCreateErrors, Map actualCandidateKeysByName)
    throws SQLException
    {
        Map stmtsByCKName = getSQLAddCandidateKeyStatements(actualCandidateKeysByName);
        Statement stmt = conn.createStatement();
        try
        {
            Iterator i = stmtsByCKName.entrySet().iterator();
            while (i.hasNext())
            {
                Map.Entry e = (Map.Entry) i.next();
                String ckName = (String) e.getKey();
                String stmtText = (String) e.getValue();
                if (JPOXLogger.DATASTORE_SCHEMA.isInfoEnabled())
                {
                    JPOXLogger.DATASTORE_SCHEMA.info(LOCALISER.msg("058200",
                        ckName, getCatalogName(), getSchemaName()));
                }

                try
                {
                    executeDdlStatement(stmt, stmtText);
                }
                catch (SQLException sqle)
                {
                    if (autoCreateErrors != null)
                    {
                        autoCreateErrors.add(sqle);
                    }
                    else
                    {
                        throw sqle;
                    }
                }
            }
        }
        finally
        {
            stmt.close();
        }
        return !stmtsByCKName.isEmpty();
    }

    /**
     * Method to drop the constraints for the table from the datastore.
     * @param conn The JDBC Connection
     * @throws SQLException Thrown when an error occurs in the JDBC call.
     */
    public void dropConstraints(Connection conn)
    throws SQLException
    {
        assertIsInitialized();

        boolean drop_using_constraint = dba.supportsAlterTableDropConstraint();
        boolean drop_using_foreign_key = dba.supportsAlterTableDropForeignKeyConstraint();
        if (!drop_using_constraint && !drop_using_foreign_key)
        {
            return;
        }

        /*
         * There's no need to drop indices; we assume they'll go away quietly
         * when the table is dropped.
         */
        HashSet fkNames = new HashSet();
        Iterator i = RDBMSStoreHelper.getForeignKeyInfoForTable(storeMgr, this, conn).iterator();
        while (i.hasNext())
        {
            ForeignKeyInfo fki = (ForeignKeyInfo) i.next();
            /*
             * Legally, JDBC drivers are allowed to return null names for
             * foreign keys. If they do, we simply have to skip DROP CONSTRAINT.
             */
            if (fki.fkName != null)
            {
                fkNames.add(fki.fkName);
            }
        }
        int numFKs = fkNames.size();
        if (numFKs > 0)
        {
            if (JPOXLogger.DATASTORE_SCHEMA.isInfoEnabled())
            {
                JPOXLogger.DATASTORE_SCHEMA.info(LOCALISER.msg("058102",
                    "" + numFKs, this));
            }
            i = fkNames.iterator();
            IdentifierFactory idFactory = storeMgr.getIdentifierFactory();
            Statement stmt = conn.createStatement();
            try
            {
                while (i.hasNext())
                {
                    String constraintName = (String) i.next();
                    String stmtText = null;
                    if (drop_using_constraint)
                    {
                        stmtText = "ALTER TABLE " + identifier + " DROP CONSTRAINT " + idFactory.getIdentifierInAdapterCase(constraintName);
                    }
                    else
                    {
                        stmtText = "ALTER TABLE " + identifier + " DROP FOREIGN KEY " + idFactory.getIdentifierInAdapterCase(constraintName);
                    }
                    if (JPOXLogger.DATASTORE_SCHEMA.isDebugEnabled())
                    {
                        JPOXLogger.DATASTORE_SCHEMA.debug(stmtText);
                    }
                    long startTime = System.currentTimeMillis();
                    stmt.execute(stmtText);
                    if (JPOXLogger.DATASTORE_SCHEMA.isDebugEnabled())
                    {
                        JPOXLogger.DATASTORE_SCHEMA.debug(LOCALISER.msg("045000",
                                (System.currentTimeMillis() - startTime)));
                    }
                    SQLWarnings.log(stmt);
                }
            }
            finally
            {
                stmt.close();
            }
        }
    }

    // ----------------------- Internal Implementation Methods -----------------

    /**
     * Accessor for the expected foreign keys for this table in the datastore.
     * Currently only checks the columns for referenced tables (i.e
     * relationships) and returns those.
     * @param clr The ClassLoaderResolver
     * @return List of foreign keys.
     */
    protected List getExpectedForeignKeys(ClassLoaderResolver clr)
    {
        assertIsInitialized();

        /*
         * The following HashSet is to avoid the duplicate usage of columns that
         * have already been used in conjunction with another column
         */
        Set colsInFKs = new HashSet();
        ArrayList foreignKeys = new ArrayList();
        Iterator i = columns.iterator();
        while (i.hasNext())
        {
            Column col = (Column) i.next();
            if (!colsInFKs.contains(col))
            {
                try
                {
                    DatastoreClass referencedTable = storeMgr.getDatastoreClass(col.getStoredJavaType(), clr);
                    if (referencedTable != null)
                    {
                        for (int j = 0; j < col.getMapping().getNumberOfDatastoreFields(); j++)
                        {
                            colsInFKs.add(col.getMapping().getDataStoreMapping(j).getDatastoreField());
                        }
                        ForeignKey fk = new ForeignKey(col.getMapping(), dba, referencedTable, true);
                        foreignKeys.add(fk);
                    }
                }
                catch (NoTableManagedException e)
                {
                    //expected when no table exists
                }
            }
        }
        return foreignKeys;
    }

    /**
     * Accessor for the expected candidate keys for this table in the datastore.
     * Currently returns an empty list.
     * @return List of candidate keys.
     */
    protected List getExpectedCandidateKeys()
    {
        assertIsInitialized();

        ArrayList candidateKeys = new ArrayList();
        return candidateKeys;
    }

    /**
     * Accessor for the indices for this table in the datastore.
     * @param clr The ClassLoaderResolver
     * @return Set of indices expected.
     */
    protected Set getExpectedIndices(ClassLoaderResolver clr)
    {
        assertIsInitialized();

        HashSet indices = new HashSet();

        /*
         * For each foreign key, add to the list an index made up of the "from"
         * column(s) of the key, *unless* those columns also happen to be
         * equal to the primary key (then they are indexed anyway).
         * Ensure that we have separate indices for foreign key columns
         * if the primary key is the combination of foreign keys, e.g. in join tables.
         * This greatly decreases deadlock probability e.g. on Oracle.
         */
        PrimaryKey pk = getPrimaryKey();
        Iterator i = getExpectedForeignKeys(clr).iterator();
        while (i.hasNext())
        {
            ForeignKey fk = (ForeignKey) i.next();
            if (!pk.getColumnList().equals(fk.getColumnList()))
            {
                indices.add(new Index(fk));
            }
        }

        return indices;
    }

    /**
     * Accessor for the primary keys for this table in the datastore.
     * @param conn The JDBC Connection
     * @return Map of primary keys
     * @throws SQLException Thrown when an error occurs in the JDBC call.
     */
    private Map getExistingPrimaryKeys(Connection conn)
    throws SQLException
    {
        DatabaseMetaData dmd = conn.getMetaData();
        String[] c = RDBMSStoreHelper.splitTableIdentifierName(dba.getCatalogSeparator(), identifier.getIdentifier());

        HashMap primaryKeysByName = new HashMap();

        // Note : the table name has no quotes here.
        ResultSet rs = dmd.getPrimaryKeys(c[0], c[1] == null ? getSchemaName() : c[1], c[2]);

        try
        {
            while (rs.next())
            {
                RDBMSIdentifierFactory idFactory = (RDBMSIdentifierFactory)storeMgr.getIdentifierFactory();
                DatastoreIdentifier pkName;
                String s = rs.getString(6);
                if (s == null)
                {
                    pkName = idFactory.newPrimaryKeyIdentifier(this);
                }
                else
                {
                    pkName = idFactory.newIdentifier(IdentifierFactory.COLUMN, s);
                }
                PrimaryKey pk = (PrimaryKey) primaryKeysByName.get(pkName);
                if (pk == null)
                {
                    pk = new PrimaryKey(this);
                    pk.setName(pkName.getIdentifier());
                    primaryKeysByName.put(pkName, pk);
                }

                int keySeq = rs.getInt(5) - 1;
                DatastoreIdentifier colName = idFactory.newIdentifier(IdentifierFactory.COLUMN, rs.getString(4));
                Column col = (Column) columnsByName.get(colName);

                if (col == null)
                {
                    throw new UnexpectedColumnException(this.toString(), colName.getIdentifier(), this.getSchemaName(), this.getCatalogName());
                }
                pk.setDatastoreField(keySeq, col);
            }
        }
        finally
        {
            if (rs.getStatement() != null)
            {
                rs.getStatement().close();
            }
            rs.close();
        }

        return primaryKeysByName;
    }

    /**
     * Accessor for the foreign keys for this table.
     * @param conn The JDBC Connection
     * @return Map of foreign keys
     * @throws SQLException Thrown when an error occurs in the JDBC call.
     */
    private Map getExistingForeignKeys(Connection conn)
    throws SQLException
    {
        HashMap foreignKeysByName = new HashMap();

        Iterator i = RDBMSStoreHelper.getForeignKeyInfoForTable(storeMgr, this, conn).iterator();
        while (i.hasNext())
        {
            RDBMSIdentifierFactory idFactory = (RDBMSIdentifierFactory)storeMgr.getIdentifierFactory();
            DatastoreIdentifier fkName;
            ForeignKeyInfo fki = (ForeignKeyInfo) i.next();
            if (fki.fkName == null)
            {
                fkName = idFactory.newForeignKeyIdentifier(this, foreignKeysByName.size());
            }
            else
            {
                fkName = idFactory.newIdentifier(IdentifierFactory.FOREIGN_KEY, fki.fkName);
            }

            boolean initiallyDeferred = fki.deferrability == DatabaseMetaData.importedKeyInitiallyDeferred;
            ForeignKey fk = (ForeignKey) foreignKeysByName.get(fkName);
            if (fk == null)
            {
                fk = new ForeignKey(initiallyDeferred);
                fk.setName(fkName.getIdentifier());
                foreignKeysByName.put(fkName, fk);
            }

            AbstractTable refTable = (AbstractTable)storeMgr.getDatastoreClass(idFactory.newDatastoreContainerIdentifier(fki.pkTableName));
            if (refTable != null)
            {
                DatastoreIdentifier colName = idFactory.newIdentifier(IdentifierFactory.COLUMN, fki.fkColumnName);
                DatastoreIdentifier refColName = idFactory.newIdentifier(IdentifierFactory.COLUMN, fki.pkColumnName);
                DatastoreField col = (DatastoreField) columnsByName.get(colName);
                DatastoreField refCol = (DatastoreField) refTable.columnsByName.get(refColName);
                if (col != null && refCol != null)
                {
                    fk.addDatastoreField(col, refCol);
                }
                else
                {
                    //TODO throw exception?
                }
            }
        }

        return foreignKeysByName;
    }

    /**
     * Accessor for the candidate keys for this table.
     * @param conn The JDBC Connection
     * @return Map of candidate keys
     * @throws SQLException Thrown when an error occurs in the JDBC call.
     */
    private Map getExistingCandidateKeys(Connection conn)
    throws SQLException
    {
        DatabaseMetaData dmd = conn.getMetaData();
        HashMap candidateKeysByName = new HashMap();
        String[] c = RDBMSStoreHelper.splitTableIdentifierName(dba.getCatalogSeparator(), identifier.getIdentifier());

        // Check for table existence first - a prerequisite
        // Note : The table name here has no quotes on it.
        ResultSet rs = dmd.getTables(c[0], c[1] == null ? getSchemaName() : c[1], c[2], null);
        boolean isRsOpen = true;
        try
        {
            if (rs.next()) // proceed if rs contains a table description for our table
            {
                RDBMSIdentifierFactory idFactory = (RDBMSIdentifierFactory)storeMgr.getIdentifierFactory();
                //close RS as soon as possible to release any locks it may have
                if (rs.getStatement() != null)
                {
                    rs.getStatement().close();
                }
                rs.close();
                isRsOpen = false;
               
                // Note : The table name has no quottes on it here. Some JDBC drivers (e.g Oracle 8i) fail here when
                // the table name is an SQL keyword. This is likely an error in the JDBC driver
                ResultSet rsIndexes = dba.getExistingIndexes(conn,dmd,c[0], c[1] == null ? getSchemaName() : c[1], c[2]);
                try
                {
                    boolean hasNext = rsIndexes.next();
                    if (!hasNext)
                    {
                        //ORACLE 10g: The SCHEMA name is mandatory for Oracle 10g, so if it was not given
                        //in the previous getExistingIndexes, it will be given here
                        rsIndexes = dba.getExistingIndexes(conn,dmd,c[0], storeMgr.getSchemaName(), c[2]);
                        hasNext = rsIndexes.next();
                    }
                    while (hasNext)
                {
                      boolean isUnique = !rsIndexes.getBoolean(4);
                      if (isUnique)
                      {
                      short idxType = rsIndexes.getShort(7);
                      if (idxType == DatabaseMetaData.tableIndexStatistic)
                      {
                                hasNext = rsIndexes.next();
                          continue;
                      }
   
                      String keyName=rsIndexes.getString(6);
                      DatastoreIdentifier idxName = idFactory.newIdentifier(IdentifierFactory.CANDIDATE_KEY, keyName);
                      CandidateKey can = (CandidateKey) candidateKeysByName.get(idxName);
                      if (can == null)
                      {
                          can = new CandidateKey(this);
                          can.setName(keyName);
     
                          candidateKeysByName.put(idxName, can);
                      }
     
                      // Set the column
                      int colSeq = rsIndexes.getShort(8) - 1;
                      DatastoreIdentifier colName = idFactory.newIdentifier(IdentifierFactory.COLUMN, rsIndexes.getString(9));
                      Column col = (Column) columnsByName.get(colName);
     
                      if (col != null)
                      {
                          can.setDatastoreField(colSeq, col);
                      }
                      }
                        hasNext = rsIndexes.next();
                }
                }
                finally
                {
                    if (rsIndexes.getStatement() != null)
                    {
                        rsIndexes.getStatement().close();
                    }
                    rsIndexes.close();
                }
            }
        }
        finally
        {
            if( isRsOpen )
            {
                if (rs.getStatement() != null)
                {
                    rs.getStatement().close();
                }
                rs.close();
            }
        }

        return candidateKeysByName;
    }

   
    /**
     * Accessor for indices on the actual table.
     * @param conn The JDBC Connection
     * @return Map of indices (keyed by the index name)
     * @throws SQLException Thrown when an error occurs in the JDBC call.
     */
    private Map getExistingIndices(Connection conn)
    throws SQLException
    {
        DatabaseMetaData dmd = conn.getMetaData();
        HashMap indicesByName = new HashMap();
        String[] c = RDBMSStoreHelper.splitTableIdentifierName(dba.getCatalogSeparator(), identifier.getIdentifier());
        // Check for table existence first - a prerequisite
        // Note : The table name here has no quotes on it.
        ResultSet rs = dmd.getTables(c[0], c[1] == null ? getSchemaName() : c[1], c[2], null);
        boolean isRsOpen = true;
        try
        {
            if (rs.next()) // proceed if rs contains a table description for our table
            {
                RDBMSIdentifierFactory idFactory = (RDBMSIdentifierFactory)storeMgr.getIdentifierFactory();
                //close RS as soon as possible to release any locks it may have
                if (rs.getStatement() != null)
                {
                    rs.getStatement().close();
                }
                rs.close();
                isRsOpen = false;

                // Note : The table name has no quotes on it here. Some JDBC drivers (e.g Oracle 9i, 10g) fail here when
                // the table name is an SQL keyword. This is likely an error in the JDBC driver
                ResultSet rsIndexes = dba.getExistingIndexes(conn,dmd,c[0], c[1] == null ? getSchemaName() : c[1], c[2]);
                try
                {
                    boolean hasNext = rsIndexes.next();
                    if (!hasNext)
                    {
                        //ORACLE 10g: The SCHEMA name is mandatory for Oracle 10g, so if it was not given
                        //in the previous getExistingIndexes, it will be given here
                        rsIndexes = dba.getExistingIndexes(conn,dmd,c[0], storeMgr.getSchemaName(), c[2]);
                        hasNext = rsIndexes.next();
                    }
                    while (hasNext)
                {
                    short idxType = rsIndexes.getShort(7);
                    if (idxType == DatabaseMetaData.tableIndexStatistic)
                    {
                            hasNext = rsIndexes.next();
                        continue;
                    }
 
                    String indexName=rsIndexes.getString(6);
                    DatastoreIdentifier idxName = idFactory.newIdentifier(IdentifierFactory.INDEX, indexName);
                    Index idx = (Index) indicesByName.get(idxName);
                    if (idx == null)
                    {
                        boolean isUnique = !rsIndexes.getBoolean(4);
   
                        idx = new Index(this, isUnique, null);
                        idx.setName(indexName);
   
                        indicesByName.put(idxName, idx);
                    }
   
                    // Set the column
                    int colSeq = rsIndexes.getShort(8) - 1;
                    DatastoreIdentifier colName = idFactory.newIdentifier(IdentifierFactory.COLUMN, rsIndexes.getString(9));
                    Column col = (Column) columnsByName.get(colName);
   
                    if (col != null)
                    {
                        idx.setColumn(colSeq, col);
                    }
                        hasNext = rsIndexes.next();
                }
                }
                finally
                {
                    if (rsIndexes.getStatement() != null)
                    {
                        rsIndexes.getStatement().close();
                    }
                    rsIndexes.close();
                }
            }
        }
        finally
        {
            if( isRsOpen )
            {
                if (rs.getStatement() != null)
                {
                    rs.getStatement().close();
                }
                rs.close();
            }
        }

        return indicesByName;
    }

    /**
     * Accessor for the SQL CREATE statements for this table.
     * @param props Properties for controlling the table creation
     * @return List of statements.
     */
    protected List getSQLCreateStatements(Properties props)
    {
        assertIsInitialized();

        ArrayList stmts = new ArrayList();
        stmts.add(dba.getCreateTableStatement(this, (Column[]) columns.toArray(new Column[columns.size()]), props));

        PrimaryKey pk = getPrimaryKey();
        if (pk.size() > 0)
        {
            // Some databases define the primary key on the create table
            // statement so we don't have a Statement for the primary key here.
            String pkStmt = dba.getAddPrimaryKeyStatement(pk, storeMgr.getIdentifierFactory());
            if (pkStmt != null)
            {
                stmts.add(pkStmt);
            }
        }

        return stmts;
    }

    /**
     * Get SQL statements to add expected Foreign Keys that are not yet at the
     * table. If the returned Map is empty, the current FK setup is correct.
     * @param actualForeignKeysByName Actual Map of foreign keys
     * @param clr The ClassLoaderResolver
     * @return a Map with the SQL statements
     */
    protected Map getSQLAddFKStatements(Map actualForeignKeysByName, ClassLoaderResolver clr)
    {
        assertIsInitialized();

        HashMap stmtsByFKName = new HashMap();
        List expectedForeignKeys = getExpectedForeignKeys(clr);
        Iterator i = expectedForeignKeys.iterator();
        int n = 1;
        RDBMSIdentifierFactory idFactory = (RDBMSIdentifierFactory)storeMgr.getIdentifierFactory();
        while (i.hasNext())
        {
            ForeignKey fk = (ForeignKey) i.next();
            if (!actualForeignKeysByName.containsValue(fk))
            {
                // If no name assigned, make one up
                if (fk.getName() == null)
                {
                    // Use the ForeignKeyIdentifier to generate the name
                    DatastoreIdentifier fkName;
                    do
                    {
                        fkName = idFactory.newForeignKeyIdentifier(this, n++);
                    }
                    while (actualForeignKeysByName.containsKey(fkName));
                    fk.setName(fkName.getIdentifier());
                }
                String stmtText = dba.getAddForeignKeyStatement(fk, idFactory);
                stmtsByFKName.put(fk.getName(), stmtText);
            }
        }

        return stmtsByFKName;
    }

    /**
     * Get SQL statements to add expected Candidate Keys that are not yet on the
     * table. If the returned Map is empty, the current Candidate Key setup is correct.
     * @param actualCandidateKeysByName Actual Map of candidate keys
     * @return a Map with the SQL statements
     */
    protected Map getSQLAddCandidateKeyStatements(Map actualCandidateKeysByName)
    {
        assertIsInitialized();

        HashMap stmtsByCKName = new HashMap();
        List expectedCandidateKeys = getExpectedCandidateKeys();
        Iterator i = expectedCandidateKeys.iterator();
        int n = 1;
        RDBMSIdentifierFactory idFactory = (RDBMSIdentifierFactory)storeMgr.getIdentifierFactory();
        while (i.hasNext())
        {
            CandidateKey ck = (CandidateKey) i.next();
            if (!actualCandidateKeysByName.containsValue(ck))
            {
                // If no name assigned, make one up
                if (ck.getName() == null)
                {
                    // Use the CandidateKeyIdentifier to generate the name
                    DatastoreIdentifier ckName;
                    do
                    {
                        ckName = idFactory.newCandidateKeyIdentifier(this, n++);
                    }
                    while (actualCandidateKeysByName.containsKey(ckName));
                    ck.setName(ckName.getIdentifier());
                }
                String stmtText = dba.getAddCandidateKeyStatement(ck, idFactory);
                stmtsByCKName.put(ck.getName(), stmtText);
            }
        }

        return stmtsByCKName;
    }

    /**
     * Utility to check if an index is necessary.
     * @param requiredIdx The index
     * @param actualIndices The actual indexes
     * @return Whether the index is needed (i.e not present in the actual indexes)
     */
    private boolean isIndexReallyNeeded(Index requiredIdx, Collection actualIndices)
    {
        Iterator i = actualIndices.iterator();
        if (requiredIdx.getName() != null)
        {
            // Compare the index name since it is defined
            IdentifierFactory idFactory = requiredIdx.getDatastoreContainerObject().getStoreManager().getIdentifierFactory();
            String reqdName = idFactory.getIdentifierInAdapterCase(requiredIdx.getName()); // Allow for user input in incorrect case
            while (i.hasNext())
            {
                Index actualIdx = (Index) i.next();
                String actualName = idFactory.getIdentifierInAdapterCase(actualIdx.getName()); // Allow for DB returning no quotes
                if (actualName.equals(reqdName) &&
                        actualIdx.getDatastoreContainerObject().getIdentifier().toString().equals(requiredIdx.getDatastoreContainerObject().getIdentifier().toString()))
                {
                    // There already is an index of that name for the same table in the actual list so not needed
                    return false;
                }
            }
        }
        else
        {
            // Compare against the index table and columns since we have no index name yet
            while (i.hasNext())
            {
                Index actualIdx = (Index) i.next();
                if (actualIdx.toString().equals(requiredIdx.toString()) &&
                    actualIdx.getDatastoreContainerObject().getIdentifier().toString().equals(requiredIdx.getDatastoreContainerObject().getIdentifier().toString()))
                {
                    // There already is an index of that name for the same table in the actual list so not needed
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Accessor for the CREATE INDEX statements for this table.
     * @param actualIndicesByName Map of actual indexes
     * @param clr The ClassLoaderResolver
     * @return Map of statements
     */
    protected Map getSQLCreateIndexStatements(Map actualIndicesByName, ClassLoaderResolver clr)
    {
        assertIsInitialized();
        HashMap stmtsByIdxName = new HashMap();
        Set expectedIndices = getExpectedIndices(clr);

        int n = 1;
        Iterator i = expectedIndices.iterator();
        RDBMSIdentifierFactory idFactory = (RDBMSIdentifierFactory)storeMgr.getIdentifierFactory();
        while (i.hasNext())
        {
            Index idx = (Index) i.next();
            if (isIndexReallyNeeded(idx, actualIndicesByName.values()))
            {
                // If no name assigned, make one up
                if (idx.getName() == null)
                {
                    // Use IndexIdentifier to generate the name.
                    DatastoreIdentifier idxName;
                    do
                    {
                        idxName = idFactory.newIndexIdentifier(this, idx.getUnique(), n++);
                        idx.setName(idxName.getIdentifier());
                    }
                    while (actualIndicesByName.containsKey(idxName));
                }

                String stmtText = dba.getCreateIndexStatement(idx, idFactory);
                stmtsByIdxName.put(idx.getName(), stmtText);
            }
        }
        return stmtsByIdxName;
    }

    /**
     * Accessor for the DROP statements for this table.
     * @return List of statements
     */
    protected List getSQLDropStatements()
    {
        assertIsInitialized();
        ArrayList stmts = new ArrayList();
        stmts.add(dba.getDropTableStatement(this));
        return stmts;
    }
}
TOP

Related Classes of org.jpox.store.rdbms.table.TableImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.