| /* |
| * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved. |
| * Copyright (c) 2020 IBM Corporation. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // Contributors: |
| // Oracle - initial API and implementation from Oracle TopLink |
| // 02/04/2013-2.5 Guy Pelletier |
| // - 389090: JPA 2.1 DDL Generation Support |
| package org.eclipse.persistence.tools.schemaframework; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.persistence.exceptions.DatabaseException; |
| import org.eclipse.persistence.internal.helper.DatabaseField; |
| import org.eclipse.persistence.internal.sessions.AbstractRecord; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.logging.SessionLog; |
| import org.eclipse.persistence.platform.database.DatabasePlatform; |
| import org.eclipse.persistence.sequencing.Sequence; |
| import org.eclipse.persistence.sequencing.TableSequence; |
| import org.eclipse.persistence.sessions.DatabaseRecord; |
| import org.eclipse.persistence.sessions.DatabaseSession; |
| import org.eclipse.persistence.sessions.Session; |
| |
| /** |
| * <b>Purpose</b>: This class is responsible for creating the tables defined in the project. |
| * A specific subclass of this class is created for each project. The specific table information |
| * is defined in the subclass. |
| * |
| * @since TopLink 2.0 |
| * @author Peter Krogh |
| */ |
| public class TableCreator { |
| /** Default identity generator sequence name. |
| * Copy of value from JPA: {@code MetadataProject.DEFAULT_IDENTITY_GENERATOR}. */ |
| public static final String DEFAULT_IDENTITY_GENERATOR = "SEQ_GEN_IDENTITY"; |
| |
| /** Flag to disable table existence check before create. */ |
| public static boolean CHECK_EXISTENCE = true; |
| |
| protected List<TableDefinition> tableDefinitions; |
| protected String name; |
| protected boolean ignoreDatabaseException; //if true, DDL generation will continue even if exceptions occur |
| |
| public TableCreator() { |
| this(new ArrayList<>()); |
| } |
| |
| public TableCreator(List<TableDefinition> tableDefinitions) { |
| super(); |
| this.tableDefinitions = tableDefinitions; |
| } |
| |
| /** |
| * Add the table. |
| */ |
| public void addTableDefinition(TableDefinition tableDefinition) { |
| this.tableDefinitions.add(tableDefinition); |
| } |
| |
| /** |
| * Add a set of tables. |
| */ |
| public void addTableDefinitions(Collection<TableDefinition> tableDefs) { |
| this.tableDefinitions.addAll(tableDefs); |
| } |
| |
| |
| /** |
| * Create constraints. |
| */ |
| public void createConstraints(DatabaseSession session) { |
| //CR2612669 |
| createConstraints(session, new SchemaManager(session)); |
| } |
| |
| /** |
| * Create constraints. |
| */ |
| public void createConstraints(DatabaseSession session, SchemaManager schemaManager) { |
| createConstraints(session, schemaManager, true); |
| } |
| |
| /** |
| * Create constraints. |
| */ |
| public void createConstraints(DatabaseSession session, SchemaManager schemaManager, boolean build) { |
| createConstraints(getTableDefinitions(), session, schemaManager, build); |
| } |
| |
| /** |
| * Create constraints. |
| */ |
| public void createConstraints(List<TableDefinition> tables, DatabaseSession session, SchemaManager schemaManager, boolean build) { |
| buildConstraints(schemaManager, build); |
| |
| // Unique constraints should be generated before foreign key constraints, |
| // because foreign key constraints can reference unique constraints |
| for (TableDefinition table : tables) { |
| try { |
| schemaManager.createUniqueConstraints(table); |
| } catch (DatabaseException ex) { |
| if (!shouldIgnoreDatabaseException()) { |
| throw ex; |
| } |
| } |
| } |
| |
| for (TableDefinition table : tables) { |
| try { |
| schemaManager.createForeignConstraints(table); |
| } catch (DatabaseException ex) { |
| if (!shouldIgnoreDatabaseException()) { |
| throw ex; |
| } |
| } |
| } |
| } |
| |
| /** |
| * This creates the tables on the database. |
| * If the table already exists this will fail. |
| */ |
| public void createTables(org.eclipse.persistence.sessions.DatabaseSession session) { |
| //CR2612669 |
| createTables(session, new SchemaManager(session)); |
| } |
| |
| /** |
| * This creates the tables on the database. |
| * If the table already exists this will fail. |
| */ |
| public void createTables(DatabaseSession session, SchemaManager schemaManager) { |
| createTables(session, schemaManager, true); |
| } |
| |
| /** |
| * This creates the tables on the database. |
| * If the table already exists this will fail. |
| */ |
| public void createTables(DatabaseSession session, SchemaManager schemaManager, boolean build) { |
| createTables(session, schemaManager, build, true, true, true); |
| } |
| |
| /** |
| * This creates the tables on the database. |
| * If the table already exists this will fail. |
| * @param session Active database session. |
| * @param schemaManager Database schema manipulation manager. |
| * @param build Whether to build constraints. |
| * @param check Whether to check for tables existence. |
| * @param createSequenceTables Whether to create sequence tables. |
| * @param createSequences Whether to create sequences. |
| */ |
| public void createTables(final DatabaseSession session, final SchemaManager schemaManager, final boolean build, |
| final boolean check, final boolean createSequenceTables, final boolean createSequences) { |
| buildConstraints(schemaManager, build); |
| |
| final String sequenceTableName = getSequenceTableName(session); |
| final List<TableDefinition> missingTables = new ArrayList<>(); |
| for (TableDefinition table : getTableDefinitions()) { |
| // Must not create sequence table as done in createSequences. |
| if (!table.getName().equals(sequenceTableName)) { |
| boolean alreadyExists = false; |
| // Check if the table already exists, to avoid logging create error. |
| if (check && CHECK_EXISTENCE && schemaManager.shouldWriteToDatabase()) { |
| alreadyExists = schemaManager.checkTableExists(table); |
| } |
| if (!alreadyExists) { |
| missingTables.add(table); |
| try { |
| schemaManager.createObject(table); |
| session.getSessionLog().log(SessionLog.FINEST, SessionLog.DDL, "default_tables_created", table.getFullName()); |
| } catch (DatabaseException ex) { |
| session.getSessionLog().log(SessionLog.FINEST, SessionLog.DDL, "default_tables_already_existed", table.getFullName()); |
| if (!shouldIgnoreDatabaseException()) { |
| throw ex; |
| } |
| } |
| } |
| } |
| } |
| |
| createConstraints(missingTables, session, schemaManager, false); |
| |
| schemaManager.createOrReplaceSequences(createSequenceTables, createSequences); |
| session.getDatasourcePlatform().initIdentitySequences(session, DEFAULT_IDENTITY_GENERATOR); |
| } |
| |
| /** |
| * Drop the table constraints from the database. |
| */ |
| public void dropConstraints(DatabaseSession session) { |
| //CR2612669 |
| dropConstraints(session, new SchemaManager(session)); |
| } |
| |
| /** |
| * Drop the table constraints from the database. |
| */ |
| public void dropConstraints(DatabaseSession session, SchemaManager schemaManager) { |
| dropConstraints(session, schemaManager, true); |
| } |
| |
| /** |
| * Drop the table constraints from the database. |
| */ |
| public void dropConstraints(DatabaseSession session, SchemaManager schemaManager, boolean build) { |
| buildConstraints(schemaManager, build); |
| |
| for (TableDefinition table : getTableDefinitions()) { |
| try { |
| schemaManager.dropConstraints(table); |
| } catch (DatabaseException exception) { |
| //ignore |
| } |
| } |
| } |
| |
| /** |
| * Drop the tables from the database. |
| */ |
| public void dropTables(DatabaseSession session) { |
| //CR2612669 |
| dropTables(session, new SchemaManager(session)); |
| } |
| |
| /** |
| * Drop the tables from the database. |
| */ |
| public void dropTables(DatabaseSession session, SchemaManager schemaManager) { |
| dropTables(session, schemaManager, true); |
| } |
| |
| /** |
| * Drop the tables from the database. |
| * @param session Active database session. |
| * @param schemaManager Database schema manipulation manager. |
| * @param build Whether to build constraints. |
| */ |
| public void dropTables(final DatabaseSession session, final SchemaManager schemaManager, final boolean build) { |
| buildConstraints(schemaManager, build); |
| |
| // CR 3870467, do not log stack, or log at all if not fine |
| boolean shouldLogExceptionStackTrace = session.getSessionLog().shouldLogExceptionStackTrace(); |
| final int level = session.getSessionLog().getLevel(); |
| if (shouldLogExceptionStackTrace) { |
| session.getSessionLog().setShouldLogExceptionStackTrace(false); |
| } |
| if (level > SessionLog.FINE) { |
| session.getSessionLog().setLevel(SessionLog.SEVERE); |
| } |
| try { |
| dropConstraints(session, schemaManager, false); |
| |
| final String sequenceTableName = getSequenceTableName(session); |
| List<TableDefinition> tables = getTableDefinitions(); |
| int trys = 1; |
| if (SchemaManager.FORCE_DROP) { |
| trys = 5; |
| } |
| while ((trys > 0) && !tables.isEmpty()) { |
| trys--; |
| final List<TableDefinition> failed = new ArrayList<>(); |
| final Set<String> tableNames = new HashSet<>(tables.size()); |
| for (final TableDefinition table : tables) { |
| final String tableName = table.getName(); |
| // Must not create sequence table as done in createSequences. |
| if (!tableName.equals(sequenceTableName)) { |
| try { |
| schemaManager.dropObject(table); |
| tableNames.add(tableName); |
| } catch (DatabaseException exception) { |
| failed.add(table); |
| if (!shouldIgnoreDatabaseException()) { |
| throw exception; |
| } |
| } |
| } |
| } |
| session.getDatasourcePlatform().removeIdentitySequences(session, DEFAULT_IDENTITY_GENERATOR, tableNames); |
| tables = failed; |
| } |
| } finally { |
| if (shouldLogExceptionStackTrace) { |
| session.getSessionLog().setShouldLogExceptionStackTrace(true); |
| } |
| if (level > SessionLog.FINE) { |
| session.getSessionLog().setLevel(level); |
| } |
| } |
| } |
| |
| /** |
| * Return the name. |
| */ |
| public String getName() { |
| return name; |
| } |
| |
| /** |
| * Return the tables. |
| */ |
| public List<TableDefinition> getTableDefinitions() { |
| return tableDefinitions; |
| } |
| |
| /** |
| * Recreate the tables on the database. |
| * This will drop the tables if they exist and recreate them. |
| */ |
| public void replaceTables(DatabaseSession session) { |
| replaceTables(session, new SchemaManager(session)); |
| } |
| |
| /** |
| * Recreate the tables on the database. |
| * This will drop the tables if they exist and recreate them. |
| */ |
| public void replaceTables(DatabaseSession session, SchemaManager schemaManager) { |
| replaceTables(session, schemaManager, true, true); |
| } |
| |
| /** |
| * Recreate the tables on the database. |
| * This will drop the tables if they exist and recreate them. |
| */ |
| public void replaceTables(DatabaseSession session, SchemaManager schemaManager, boolean createSequenceTables) { |
| replaceTables(session, schemaManager, createSequenceTables, false); |
| } |
| |
| /** |
| * Recreate the tables on the database. |
| * This will drop the tables if they exist and recreate them. |
| */ |
| public void replaceTables(DatabaseSession session, SchemaManager schemaManager, boolean createSequenceTables, boolean createSequences) { |
| replaceTablesAndConstraints(schemaManager, session, createSequenceTables, createSequences); |
| } |
| |
| protected void replaceTablesAndConstraints(SchemaManager schemaManager, DatabaseSession session, boolean createSequenceTables, boolean createSequences) { |
| buildConstraints(schemaManager, true); |
| boolean ignore = shouldIgnoreDatabaseException(); |
| setIgnoreDatabaseException(true); |
| try { |
| dropTables(session, schemaManager, false); |
| } finally { |
| setIgnoreDatabaseException(ignore); |
| } |
| createTables(session, schemaManager, false, false, createSequenceTables, createSequences); |
| } |
| |
| protected void replaceTablesAndConstraints(SchemaManager schemaManager, DatabaseSession session) { |
| replaceTables(session, schemaManager, false, false); |
| } |
| |
| /** |
| * Convert any field constraint to constraint objects. |
| */ |
| protected void buildConstraints(SchemaManager schemaManager, boolean build) { |
| if (build) { |
| for (TableDefinition table : getTableDefinitions()) { |
| schemaManager.buildFieldTypes(table); |
| } |
| } |
| } |
| |
| /** |
| * Set the name. |
| */ |
| public void setName(String name) { |
| this.name = name; |
| } |
| |
| /** |
| * Set the tables. |
| */ |
| public void setTableDefinitions(List<TableDefinition> tableDefinitions) { |
| this.tableDefinitions = tableDefinitions; |
| } |
| |
| /** |
| * Return true if DatabaseException is to be ignored. |
| */ |
| public boolean shouldIgnoreDatabaseException() { |
| return ignoreDatabaseException; |
| } |
| |
| /** |
| * Set flag whether DatabaseException should be ignored. |
| */ |
| public void setIgnoreDatabaseException(boolean ignoreDatabaseException) { |
| this.ignoreDatabaseException = ignoreDatabaseException; |
| } |
| |
| /** |
| * This returns the Sequence Table's qualified name, without delimiting. |
| * @return the qualified table name |
| */ |
| protected String getSequenceTableName(Session session) { |
| String sequenceTableName = null; |
| if (session.getProject().usesSequencing()) { |
| Sequence sequence = session.getLogin().getDefaultSequence(); |
| if (sequence instanceof TableSequence) { |
| sequenceTableName = ((TableSequence)sequence).getQualifiedTableName(); |
| } |
| } |
| return sequenceTableName; |
| } |
| |
| /** |
| * Create or extend the tables on the database. |
| * This will alter existing tables to add missing fields or create the table otherwise. |
| * It will also create Sequences tables and objects. |
| */ |
| public void extendTables(DatabaseSession session, SchemaManager schemaManager) { |
| extendTablesAndConstraints(schemaManager, session); |
| schemaManager.createOrReplaceSequences(true, true); |
| } |
| |
| protected void extendTablesAndConstraints(SchemaManager schemaManager, DatabaseSession session) { |
| buildConstraints(schemaManager, true); |
| boolean ignore = shouldIgnoreDatabaseException(); |
| setIgnoreDatabaseException(true); |
| try { |
| extendTables(session, schemaManager, false); |
| } finally { |
| setIgnoreDatabaseException(ignore); |
| } |
| } |
| |
| /** |
| * This creates/extends the tables on the database. |
| * @param session Active database session. |
| * @param schemaManager Database schema manipulation manager. |
| * @param build Whether to build constraints. |
| */ |
| public void extendTables(final DatabaseSession session, final SchemaManager schemaManager, final boolean build) { |
| buildConstraints(schemaManager, build); |
| |
| final String sequenceTableName = getSequenceTableName(session); |
| for (final TableDefinition table : getTableDefinitions()) { |
| // Must not create sequence table as done in createSequences. |
| if (!table.getName().equals(sequenceTableName)) { |
| final AbstractSession abstractSession = (AbstractSession) session; |
| boolean alreadyExists = false; |
| // Check if the table already exists, to avoid logging create error. |
| if (CHECK_EXISTENCE && schemaManager.shouldWriteToDatabase()) { |
| alreadyExists = schemaManager.checkTableExists(table); |
| } |
| DatabaseException createTableException = null; |
| if (!alreadyExists) { |
| //assume table does not exist |
| try { |
| schemaManager.createObject(table); |
| session.getSessionLog().log(SessionLog.FINEST, SessionLog.DDL, "default_tables_created", table.getFullName()); |
| } catch (final DatabaseException exception) { |
| createTableException = exception; |
| alreadyExists = true; |
| } |
| } |
| if (alreadyExists) { |
| //Assume the table exists, so lookup the column info |
| |
| //While SQL is case insensitive, getColumnInfo is and will not return the table info unless the name is passed in |
| //as it is stored internally. |
| String tableName = table.getTable()==null? table.getName(): table.getTable().getName(); |
| final boolean usesDelimiting = (table.getTable()!=null && table.getTable().shouldUseDelimiters()); |
| List<AbstractRecord> columnInfo = null; |
| |
| //I need the actual table catalog, schema and tableName for getTableInfo. |
| columnInfo = abstractSession.getAccessor().getColumnInfo(null, null, tableName, null, abstractSession); |
| |
| if (!usesDelimiting && (columnInfo == null || columnInfo.isEmpty()) ) { |
| tableName = tableName.toUpperCase(); |
| columnInfo = abstractSession.getAccessor().getColumnInfo(null, null, tableName, null, abstractSession); |
| if (( columnInfo == null || columnInfo.isEmpty()) ){ |
| tableName = tableName.toLowerCase(); |
| columnInfo = abstractSession.getAccessor().getColumnInfo(null, null, tableName, null, abstractSession); |
| } |
| } |
| if (columnInfo != null && !columnInfo.isEmpty()) { |
| //Table exists, add individual fields as necessary |
| |
| //hash the table's existing columns by name |
| final Map<DatabaseField, AbstractRecord> columns = new HashMap<>(columnInfo.size()); |
| final DatabaseField columnNameLookupField = new DatabaseField("COLUMN_NAME"); |
| final DatabaseField schemaLookupField = new DatabaseField("TABLE_SCHEM"); |
| boolean schemaMatchFound = false; |
| // Determine the probably schema for the table, this is a heuristic, so should not cause issues if wrong. |
| String qualifier = table.getQualifier(); |
| if ((qualifier == null) || (qualifier.length() == 0)) { |
| qualifier = session.getDatasourcePlatform().getTableQualifier(); |
| if ((qualifier == null) || (qualifier.length() == 0)) { |
| qualifier = session.getLogin().getUserName(); |
| // Oracle DB DS defined in WLS does not contain user name so it's stored in platform. |
| if ((qualifier == null) || (qualifier.length() == 0)) { |
| final DatabasePlatform platform = session.getPlatform(); |
| if (platform.supportsConnectionUserName()) { |
| qualifier = platform.getConnectionUserName(); |
| } |
| } |
| } |
| } |
| final boolean checkSchema = (qualifier != null) && (qualifier.length() > 0); |
| for (final AbstractRecord record : columnInfo) { |
| final String fieldName = (String)record.get(columnNameLookupField); |
| if (fieldName != null && fieldName.length() > 0) { |
| final DatabaseField column = new DatabaseField(fieldName); |
| if (session.getPlatform().shouldForceFieldNamesToUpperCase()) { |
| column.useUpperCaseForComparisons(true); |
| } |
| final String schema = (String)record.get(schemaLookupField); |
| // Check the schema as well. Ignore columns for other schema if a schema match is found. |
| if (schemaMatchFound) { |
| if (qualifier.equalsIgnoreCase(schema)) { |
| columns.put(column, record); |
| } |
| } else { |
| if (checkSchema) { |
| if (qualifier.equalsIgnoreCase(schema)) { |
| schemaMatchFound = true; |
| // Remove unmatched columns from other schemas. |
| columns.clear(); |
| } |
| } |
| // If none of the schemas match what is expected, assume what is expected is wrong, and use all columns. |
| columns.put(column, record); |
| } |
| } |
| } |
| |
| //Go through each field we need to have in the table to see if it already exists |
| for (final FieldDefinition fieldDef : table.getFields()){ |
| DatabaseField dbField = fieldDef.getDatabaseField(); |
| if ( dbField == null ) { |
| dbField = new DatabaseField(fieldDef.getName()); |
| } |
| if (columns.get(dbField)== null) { |
| //field does not exist so add it to the table |
| try { |
| table.addFieldOnDatabase(abstractSession, fieldDef); |
| } catch (final DatabaseException addFieldEx) { |
| session.getSessionLog().log(SessionLog.FINEST, SessionLog.DDL, "cannot_add_field_to_table", dbField.getName(), table.getFullName(), addFieldEx.getMessage()); |
| if (!shouldIgnoreDatabaseException()) { |
| throw addFieldEx; |
| } |
| } |
| } |
| } |
| } else if (createTableException != null) { |
| session.getSessionLog().log(SessionLog.FINEST, SessionLog.DDL, "cannot_create_table", table.getFullName(), createTableException.getMessage()); |
| if (!shouldIgnoreDatabaseException()) { |
| throw createTableException; |
| } |
| } |
| } |
| } |
| } |
| createConstraints(session, schemaManager, false); |
| |
| schemaManager.createSequences(); |
| session.getDatasourcePlatform().initIdentitySequences(session, DEFAULT_IDENTITY_GENERATOR); |
| |
| } |
| } |