| /* |
| * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved. |
| * Copyright (c) 1998, 2015 Sei Syvalta. 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 |
| // Sei Syvalta - Bug 330237 - Tables are created in unspecified order (DDL creation) |
| // 01/06/2011-2.3 Guy Pelletier |
| // - 312244: can't map optional one-to-one relationship using @PrimaryKeyJoinColumn |
| // 04/05/2011-2.3 Guy Pelletier |
| // - 337323: Multi-tenant with shared schema support (part 3) |
| // 09/09/2011-2.3.1 Guy Pelletier |
| // - 356197: Add new VPD type to MultitenantType |
| // 11/10/2011-2.4 Guy Pelletier |
| // - 357474: Address primaryKey option from tenant discriminator column |
| // 14/05/2012-2.4 Guy Pelletier |
| // - 376603: Provide for table per tenant support for multitenant applications |
| // 31/05/2012-2.4 Guy Pelletier |
| // - 381196: Multitenant persistence units with a dedicated emf should allow for DDL generation. |
| // 12/07/2012-2.5 Guy Pelletier |
| // - 389090: JPA 2.1 DDL Generation Support (foreign key metadata support) |
| // 04/04/2013-2.4.3 Guy Pelletier |
| // - 388564: Generated DDL does not match annotation |
| package org.eclipse.persistence.tools.schemaframework; |
| |
| import java.sql.DatabaseMetaData; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| import org.eclipse.persistence.eis.EISDescriptor; |
| import org.eclipse.persistence.exceptions.DatabaseException; |
| import org.eclipse.persistence.exceptions.ValidationException; |
| import org.eclipse.persistence.internal.databaseaccess.DatabasePlatform; |
| import org.eclipse.persistence.internal.databaseaccess.FieldTypeDefinition; |
| import org.eclipse.persistence.internal.descriptors.FieldTransformation; |
| import org.eclipse.persistence.internal.descriptors.MethodBasedFieldTransformation; |
| import org.eclipse.persistence.internal.descriptors.TransformerBasedFieldTransformation; |
| import org.eclipse.persistence.internal.helper.ClassConstants; |
| import org.eclipse.persistence.internal.helper.ConversionManager; |
| import org.eclipse.persistence.internal.helper.DatabaseField; |
| import org.eclipse.persistence.internal.helper.DatabaseTable; |
| import org.eclipse.persistence.internal.helper.Helper; |
| import org.eclipse.persistence.internal.queries.ContainerPolicy; |
| import org.eclipse.persistence.internal.queries.MappedKeyMapContainerPolicy; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.logging.AbstractSessionLog; |
| import org.eclipse.persistence.logging.SessionLog; |
| import org.eclipse.persistence.mappings.AggregateCollectionMapping; |
| import org.eclipse.persistence.mappings.DatabaseMapping; |
| import org.eclipse.persistence.mappings.DirectCollectionMapping; |
| import org.eclipse.persistence.mappings.DirectMapMapping; |
| import org.eclipse.persistence.mappings.DirectToFieldMapping; |
| import org.eclipse.persistence.mappings.ForeignReferenceMapping; |
| import org.eclipse.persistence.mappings.ManyToManyMapping; |
| import org.eclipse.persistence.mappings.OneToManyMapping; |
| import org.eclipse.persistence.mappings.OneToOneMapping; |
| import org.eclipse.persistence.mappings.RelationTableMechanism; |
| import org.eclipse.persistence.mappings.TransformationMapping; |
| import org.eclipse.persistence.mappings.converters.Converter; |
| import org.eclipse.persistence.mappings.converters.SerializedObjectConverter; |
| import org.eclipse.persistence.mappings.converters.TypeConversionConverter; |
| import org.eclipse.persistence.mappings.structures.ObjectRelationalDataTypeDescriptor; |
| import org.eclipse.persistence.oxm.XMLDescriptor; |
| import org.eclipse.persistence.sequencing.DefaultSequence; |
| import org.eclipse.persistence.sequencing.NativeSequence; |
| import org.eclipse.persistence.sequencing.Sequence; |
| import org.eclipse.persistence.sessions.DatabaseLogin; |
| import org.eclipse.persistence.sessions.Project; |
| import org.eclipse.persistence.sessions.Session; |
| import org.eclipse.persistence.sessions.server.ServerSession; |
| |
| /** |
| * DefaultTableGenerator is a utility class used to generate a default table schema for a EclipseLink project object. |
| * |
| * The utility can be used in EclipseLink CMP for OC4J to perform the table auto creation process, which can be triggered |
| * at deployment time when EclipseLink project descriptor is absent (default mapping) or present. |
| * |
| * The utility can also be used to any EclipseLink application to perform the table drop/creation at runtime. |
| * |
| * The utility handles all direct/relational mappings, inheritance, multiple tables, interface with/without tables, |
| * optimistic version/timestamp lockings, nested relationships, BLOB/CLOB generation. |
| * |
| * The utility is platform-agnostic. |
| * |
| * Usage: |
| * - CMP |
| * 1. set "autocreate-tables=true|false, autodelete-tables=true|false" in oc4j application deployment |
| * descriptor files (config/system-application.xml, config/application.xml, or orion-application.xml in an .ear) |
| * |
| * 2. Default Mapping: the same as CMP, plus system properties setting -Declipselink.defaultmapping.autocreate-tables='true|false' |
| * and -Declipselink.defaultmapping.autodelete-tables='true|false' |
| * |
| * - Non-CMP: |
| * 1. Configuration: through sessions.xml |
| * 2. Directly runtime call through schema framework: |
| * SchemaManager mgr = new SchemaManager(session); |
| * mgr.replaceDefaultTables(); //drop and create |
| * mgr.createDefaultTables(); //create only |
| * |
| * The utility currently only supports relational project. |
| * |
| * @author King Wang |
| * @since Oracle TopLink 10.1.3 |
| */ |
| public class DefaultTableGenerator { |
| /** The project object used to generate the default data schema. */ |
| Project project = null; |
| |
| /** the target database platform. */ |
| protected DatabasePlatform databasePlatform; |
| |
| /** |
| * Used to track the table definition: keyed by the table name, and valued |
| * by the table definition object. |
| */ |
| protected Map<String, TableDefinition> tableMap = null; |
| |
| /** |
| * Used to track the field definition: keyed by the database field object, and |
| * valued by the field definition. |
| */ |
| protected Map<DatabaseField, FieldDefinition> fieldMap = null; |
| |
| /** DatabaseField pool (synchronized with above 'fieldMap') */ |
| protected Map<DatabaseField, DatabaseField> databaseFields; |
| |
| /** When this flag is 'false' EclipseLink will not attempt to create fk constraints. */ |
| protected boolean generateFKConstraints; |
| |
| /** |
| * Default constructor |
| */ |
| public DefaultTableGenerator(Project project) { |
| this.project = project; |
| if (project.getDatasourceLogin().getDatasourcePlatform() instanceof DatabasePlatform){ |
| this.databasePlatform = (DatabasePlatform)project.getDatasourceLogin().getDatasourcePlatform(); |
| this.generateFKConstraints = this.databasePlatform.supportsForeignKeyConstraints(); |
| } |
| this.tableMap = new LinkedHashMap<>(); |
| this.fieldMap = new LinkedHashMap<>(); |
| this.databaseFields = new LinkedHashMap<>(); |
| } |
| |
| /** |
| * This constructor will create a DefaultTableGenerator that can be set to create fk |
| * constraints |
| */ |
| public DefaultTableGenerator(Project project, boolean generateFKConstraints){ |
| this(project); |
| this.generateFKConstraints = generateFKConstraints; |
| } |
| |
| /** |
| * Generate a default TableCreator object from the EclipseLink project object. |
| */ |
| public TableCreator generateDefaultTableCreator() { |
| TableCreator tblCreator = new TableCreator(); |
| |
| //go through each descriptor and build the table/field definitions out of mappings |
| for (ClassDescriptor descriptor : this.project.getOrderedDescriptors()) { |
| |
| if ((descriptor instanceof XMLDescriptor) || (descriptor instanceof EISDescriptor) || (descriptor instanceof ObjectRelationalDataTypeDescriptor)) { |
| //default table generator does not support ox, eis and object-relational descriptor |
| AbstractSessionLog.getLog().log(SessionLog.WARNING, SessionLog.DDL, "relational_descriptor_support_only", null, true); |
| |
| return tblCreator; |
| } |
| |
| // Aggregate descriptors do not contain table/field data and are |
| // processed through their owning entities. Aggregate descriptors |
| // can not exist on their own. |
| // Table per tenant descriptors will not be initialized. |
| if (!descriptor.isDescriptorTypeAggregate() && ! (descriptor.hasTablePerMultitenantPolicy() && ! project.allowTablePerMultitenantDDLGeneration())) { |
| initTableSchema(descriptor); |
| } |
| } |
| |
| //Post init the schema for relation table and direct collection/map tables, and several special mapping handlings. |
| for (ClassDescriptor descriptor : this.project.getOrderedDescriptors()) { |
| // Aggregate descriptors do not contain table/field data and are |
| // processed through their owning entities. Aggregate descriptors |
| // can not exist on their own. |
| // Table per tenant descriptors will not be initialized. |
| if (!descriptor.isAggregateDescriptor() && !descriptor.isAggregateCollectionDescriptor() && ! (descriptor.hasTablePerMultitenantPolicy() && ! project.allowTablePerMultitenantDDLGeneration())) { |
| postInitTableSchema(descriptor); |
| |
| // If VPD descriptor we need to generate some DDL for its default table. |
| if (descriptor.hasMultitenantPolicy()) { |
| descriptor.getMultitenantPolicy().addToTableDefinition(getTableDefFromDBTable(descriptor.getDefaultTable())); |
| } |
| } |
| } |
| |
| tblCreator.addTableDefinitions(tableMap.values()); |
| |
| return tblCreator; |
| } |
| |
| /** |
| * Generate a default TableCreator object from the EclipseLink project object, |
| * and perform the table existence check through jdbc table metadata, and filter out |
| * tables which are already in the database. |
| */ |
| public TableCreator generateFilteredDefaultTableCreator(AbstractSession session) throws DatabaseException { |
| TableCreator tblCreator = generateDefaultTableCreator(); |
| |
| try { |
| //table exisitence check. |
| java.sql.Connection conn = null; |
| if (session.isServerSession()) { |
| //acquire a connection from the pool |
| conn = ((ServerSession)session).getDefaultConnectionPool().acquireConnection().getConnection(); |
| } else if (session.isDatabaseSession()) { |
| conn = session.getAccessor().getConnection(); |
| } |
| if (conn == null) { |
| //TODO: this is not pretty, connection is not obtained for some reason. |
| return tblCreator; |
| } |
| DatabaseMetaData dbMetaData = conn.getMetaData(); |
| ResultSet resultSet = dbMetaData.getTables(null, dbMetaData.getUserName(), null, new String[] { "TABLE" }); |
| List<String> tablesInDatabase = new ArrayList<>(); |
| |
| while (resultSet.next()) { |
| //save all tables from the database |
| tablesInDatabase.add(resultSet.getString("TABLE_NAME")); |
| } |
| |
| resultSet.close(); |
| |
| List<TableDefinition> existedTables = new ArrayList<>(); |
| List<String> existedTableNames = new ArrayList<>(); |
| Iterator<TableDefinition> tblDefIter = tblCreator.getTableDefinitions().iterator(); |
| |
| while (tblDefIter.hasNext()) { |
| TableDefinition tblDef = tblDefIter.next(); |
| |
| //check if the to-be-created table is already in the database |
| if (tablesInDatabase.contains(tblDef.getFullName())) { |
| existedTables.add(tblDef); |
| existedTableNames.add(tblDef.getFullName()); |
| } |
| } |
| |
| if (!existedTableNames.isEmpty()) { |
| session.getSessionLog().log(SessionLog.FINEST, SessionLog.DDL, "skip_create_existing_tables", existedTableNames); |
| |
| //remove the existed tables, won't create them. |
| tblCreator.getTableDefinitions().removeAll(existedTables); |
| } |
| } catch (SQLException sqlEx) { |
| throw DatabaseException.errorRetrieveDbMetadataThroughJDBCConnection(); |
| } |
| |
| return tblCreator; |
| } |
| |
| /** |
| * Build tables/fields information into the table creator object from a EclipseLink descriptor. |
| * This should handle most of the direct/relational mappings except many-to-many and direct |
| * collection/map mappings, which must be down in postInit method. |
| */ |
| protected void initTableSchema(ClassDescriptor descriptor) { |
| TableDefinition tableDefintion = null; |
| if (descriptor.hasTablePerClassPolicy() && descriptor.isAbstract()) { |
| return; |
| } |
| |
| //create a table definition for each mapped database table |
| for (DatabaseTable table : descriptor.getTables()) { |
| tableDefintion = getTableDefFromDBTable(table); |
| } |
| |
| //build each field definition and figure out which table it goes |
| for (DatabaseField dbField : descriptor.getFields()) { |
| if (dbField.isCreatable()) { |
| boolean isPKField = false; |
| |
| //first check if the field is a pk field in the default table. |
| isPKField = descriptor.getPrimaryKeyFields().contains(dbField); |
| |
| //then check if the field is a pk field in the secondary table(s), this is only applied to the multiple tables case. |
| Map<DatabaseField, DatabaseField> secondaryKeyMap = descriptor.getAdditionalTablePrimaryKeyFields().get(dbField.getTable()); |
| |
| if (secondaryKeyMap != null) { |
| isPKField = isPKField || secondaryKeyMap.containsValue(dbField); |
| } |
| |
| // Now check if it is a tenant discriminat column primary key field. |
| isPKField = isPKField || dbField.isPrimaryKey(); |
| |
| //build or retrieve the field definition. |
| FieldDefinition fieldDef = getFieldDefFromDBField(dbField); |
| if (isPKField) { |
| fieldDef.setIsPrimaryKey(true); |
| // Check if the generation strategy is IDENTITY |
| String sequenceName = descriptor.getSequenceNumberName(); |
| DatabaseLogin login = this.project.getLogin(); |
| Sequence seq = login.getSequence(sequenceName); |
| if(seq instanceof DefaultSequence) { |
| seq = login.getDefaultSequence(); |
| } |
| //The native sequence whose value should be acquired after insert is identity sequence |
| boolean isIdentity = seq instanceof NativeSequence && seq.shouldAcquireValueAfterInsert(); |
| fieldDef.setIsIdentity(isIdentity); |
| } |
| |
| //find the table the field belongs to, and add it to the table, only if not already added. |
| tableDefintion = this.tableMap.get(dbField.getTableName()); |
| |
| if ((tableDefintion != null) && !tableDefintion.getFields().contains(fieldDef)) { |
| tableDefintion.addField(fieldDef); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Build additional table/field definitions for the descriptor, like relation table |
| * and direct-collection, direct-map table, as well as reset LOB type for serialized |
| * object mapping and type conversion mapping for LOB usage |
| */ |
| protected void postInitTableSchema(ClassDescriptor descriptor) { |
| for (DatabaseMapping mapping : descriptor.getMappings()) { |
| if (descriptor.isChildDescriptor() && descriptor.getInheritancePolicy().getParentDescriptor().getMappingForAttributeName(mapping.getAttributeName()) != null) { |
| // If we are an inheritance subclass, do nothing. That is, don't |
| // generate mappings that will be generated by our parent, |
| // otherwise the fields for that mapping will be generated n |
| // times for the same table. |
| continue; |
| } else if (mapping.isManyToManyMapping()) { |
| buildRelationTableDefinition((ManyToManyMapping)mapping, ((ManyToManyMapping)mapping).getRelationTableMechanism(), ((ManyToManyMapping)mapping).getListOrderField(), mapping.getContainerPolicy()); |
| } else if (mapping.isDirectCollectionMapping()) { |
| buildDirectCollectionTableDefinition((DirectCollectionMapping) mapping, descriptor); |
| } else if (mapping.isDirectToFieldMapping()) { |
| Converter converter = ((DirectToFieldMapping)mapping).getConverter(); |
| if (converter != null) { |
| if (converter instanceof TypeConversionConverter) { |
| resetFieldTypeForLOB((DirectToFieldMapping)mapping); |
| } |
| |
| if (converter instanceof SerializedObjectConverter) { |
| //serialized object mapping field should be BLOB/IMAGE |
| getFieldDefFromDBField(mapping.getField()).setType(((SerializedObjectConverter)converter).getSerializer().getType()); |
| } |
| } |
| } else if (mapping.isAggregateCollectionMapping()) { |
| //need to figure out the target foreign key field and add it into the aggregate target table |
| createAggregateTargetTable((AggregateCollectionMapping) mapping); |
| } else if (mapping.isForeignReferenceMapping()) { |
| if (mapping.isOneToOneMapping()) { |
| RelationTableMechanism relationTableMechanism = ((OneToOneMapping)mapping).getRelationTableMechanism(); |
| if(relationTableMechanism == null) { |
| addForeignKeyFieldToSourceTargetTable((OneToOneMapping) mapping); |
| } else { |
| buildRelationTableDefinition((OneToOneMapping)mapping, relationTableMechanism, null, null); |
| } |
| } else if (mapping.isOneToManyMapping()) { |
| addForeignKeyFieldToSourceTargetTable((OneToManyMapping) mapping); |
| TableDefinition targTblDef = getTableDefFromDBTable(mapping.getReferenceDescriptor().getDefaultTable()); |
| addFieldsForMappedKeyMapContainerPolicy(mapping.getContainerPolicy(), targTblDef); |
| } |
| } else if (mapping.isTransformationMapping()) { |
| resetTransformedFieldType((TransformationMapping) mapping); |
| } else if (mapping.isAggregateObjectMapping()){ |
| postInitTableSchema(mapping.getReferenceDescriptor()); |
| } |
| } |
| |
| processAdditionalTablePkFields(descriptor); |
| } |
| |
| |
| /** |
| * The ContainerPolicy may contain some additional fields that should be added to the table |
| * |
| * @see MappedKeyMapContainerPolicy |
| */ |
| protected void addFieldsForMappedKeyMapContainerPolicy(ContainerPolicy cp, TableDefinition table){ |
| if (cp.isMappedKeyMapPolicy()){ |
| List<DatabaseField> keyFields = cp.getIdentityFieldsForMapKey(); |
| Iterator<DatabaseField> i = keyFields.iterator(); |
| while (i.hasNext()){ |
| DatabaseField foreignKey = i.next(); |
| FieldDefinition fieldDef = getFieldDefFromDBField(foreignKey); |
| if (!table.getFields().contains(fieldDef)) { |
| table.addField(fieldDef); |
| } |
| } |
| Map<DatabaseField, DatabaseField> foreignKeys = ((MappedKeyMapContainerPolicy)cp).getForeignKeyFieldsForMapKey(); |
| if (foreignKeys != null){ |
| addForeignMappingFkConstraint(foreignKeys, false); |
| } |
| } |
| } |
| |
| /** |
| * Build relation table definitions for all many-to-many relationships in a EclipseLink descriptor. |
| */ |
| protected void buildRelationTableDefinition(ForeignReferenceMapping mapping, RelationTableMechanism relationTableMechanism, DatabaseField listOrderField, ContainerPolicy cp) { |
| //first create relation table |
| TableDefinition table = getTableDefFromDBTable(relationTableMechanism.getRelationTable()); |
| |
| //add source foreign key fields into the relation table |
| List<DatabaseField> srcFkFields = relationTableMechanism.getSourceRelationKeyFields(); |
| List<DatabaseField> srcKeyFields = relationTableMechanism.getSourceKeyFields(); |
| |
| buildRelationTableFields(mapping, table, srcFkFields, srcKeyFields); |
| |
| //add target foreign key fields into the relation table |
| List<DatabaseField> targFkFields = relationTableMechanism.getTargetRelationKeyFields(); |
| List<DatabaseField> targKeyFields = relationTableMechanism.getTargetKeyFields(); |
| |
| buildRelationTableFields(mapping, table, targFkFields, targKeyFields); |
| |
| if (cp != null){ |
| addFieldsForMappedKeyMapContainerPolicy(cp, table); |
| } |
| |
| if (listOrderField != null) { |
| FieldDefinition fieldDef = getFieldDefFromDBField(listOrderField); |
| if (!table.getFields().contains(fieldDef)) { |
| table.addField(fieldDef); |
| } |
| } |
| } |
| |
| /** |
| * Build field definitions and foreign key constraints for all many-to-many relation table. |
| */ |
| protected void buildRelationTableFields(ForeignReferenceMapping mapping, TableDefinition table, List<DatabaseField> fkFields, List<DatabaseField> targetFields) { |
| assert fkFields.size() > 0 && fkFields.size() == targetFields.size(); |
| |
| DatabaseField fkField = null; |
| DatabaseField targetField = null; |
| List<String> fkFieldNames = new ArrayList<>(); |
| List<String> targetFieldNames = new ArrayList<>(); |
| |
| for (int index = 0; index < fkFields.size(); index++) { |
| fkField = fkFields.get(index); |
| targetField = targetFields.get(index); |
| fkFieldNames.add(fkField.getNameDelimited(databasePlatform)); |
| targetFieldNames.add(targetField.getNameDelimited(databasePlatform)); |
| |
| fkField = resolveDatabaseField(fkField, targetField); |
| setFieldToRelationTable(fkField, table); |
| } |
| |
| // add a foreign key constraint from fk field to target field |
| DatabaseTable targetTable = targetField.getTable(); |
| TableDefinition targetTblDef = getTableDefFromDBTable(targetTable); |
| |
| if (mapping.getDescriptor().hasTablePerClassPolicy()) { |
| return; |
| } |
| if (mapping.getReferenceDescriptor().hasTablePerClassPolicy() |
| && mapping.getReferenceDescriptor().getTablePerClassPolicy().hasChild()) { |
| return; |
| } |
| addForeignKeyConstraint(table, targetTblDef, fkFieldNames, targetFieldNames, mapping.isCascadeOnDeleteSetOnDatabase()); |
| } |
| |
| /** |
| * Build direct collection table definitions in a EclipseLink descriptor |
| */ |
| protected void buildDirectCollectionTableDefinition(DirectCollectionMapping mapping, ClassDescriptor descriptor) { |
| //first create direct collection table |
| TableDefinition table = getTableDefFromDBTable(mapping.getReferenceTable()); |
| |
| DatabaseField dbField = null; |
| DatabaseField targetField = null; |
| List<String> fkFieldNames = new ArrayList<>(); |
| List<String> targetFieldNames = new ArrayList<>(); |
| List<DatabaseField> fkFields = mapping.getReferenceKeyFields(); |
| List<DatabaseField> targetFields = mapping.getSourceKeyFields(); |
| for (int index = 0; index < fkFields.size(); index++) { |
| DatabaseField fkField = fkFields.get(index); |
| targetField = targetFields.get(index); |
| fkFieldNames.add(fkField.getNameDelimited(databasePlatform)); |
| targetFieldNames.add(targetField.getNameDelimited(databasePlatform)); |
| |
| fkField = resolveDatabaseField(fkField, targetField); |
| FieldDefinition fieldDef = getFieldDefFromDBField(fkField); |
| // Avoid adding fields twice for table per class. |
| if (!table.getFields().contains(fieldDef)) { |
| table.addField(fieldDef); |
| } |
| } |
| |
| // add a foreign key constraint from fk field to target field |
| DatabaseTable targetTable = targetField.getTable(); |
| TableDefinition targetTblDef = getTableDefFromDBTable(targetTable); |
| |
| //add the direct collection field to the table. |
| FieldDefinition fieldDef = getFieldDefFromDBField(mapping.getDirectField()); |
| if (!table.getFields().contains(fieldDef)) { |
| table.addField(fieldDef); |
| } |
| |
| //if the mapping is direct-map field, add the direct key field to the table as well. |
| // TODO: avoid generating DDL for map key mappings for the time being. |
| // Bug: 270814 |
| if (mapping.isDirectMapMapping() && ! mapping.getContainerPolicy().isMappedKeyMapPolicy() ) { |
| dbField = ((DirectMapMapping) mapping).getDirectKeyField(); |
| fieldDef = getFieldDefFromDBField(dbField); |
| if (!table.getFields().contains(fieldDef)) { |
| table.addField(fieldDef); |
| } |
| } else { |
| addFieldsForMappedKeyMapContainerPolicy(mapping.getContainerPolicy(), table); |
| |
| if (mapping.getListOrderField() != null) { |
| fieldDef = getFieldDefFromDBField(mapping.getListOrderField()); |
| if (!table.getFields().contains(fieldDef)) { |
| table.addField(fieldDef); |
| } |
| } |
| } |
| if (mapping.getDescriptor().hasTablePerClassPolicy()) { |
| return; |
| } |
| addForeignKeyConstraint(table, targetTblDef, fkFieldNames, targetFieldNames, mapping.isCascadeOnDeleteSetOnDatabase()); |
| } |
| |
| /** |
| * Reset field type to use BLOB/CLOB with type conversion mapping fix for 4k oracle thin driver bug. |
| */ |
| protected void resetFieldTypeForLOB(DirectToFieldMapping mapping) { |
| if (mapping.getFieldClassification().getName().equals("java.sql.Blob")) { |
| //allow the platform to figure out what database field type gonna be used. |
| //For example, Oracle9 will generate BLOB type, SQL Server generats IMAGE. |
| getFieldDefFromDBField(mapping.getField()).setType(Byte[].class); |
| } else if (mapping.getFieldClassification().getName().equals("java.sql.Clob")) { |
| //allow the platform to figure out what database field type gonna be used. |
| //For example, Oracle9 will generate CLOB type. SQL Server generats TEXT. |
| getFieldDefFromDBField(mapping.getField()).setType(Character[].class); |
| } |
| } |
| |
| /** |
| * Reset the transformation mapping field types |
| */ |
| protected void resetTransformedFieldType(TransformationMapping mapping) { |
| Iterator<FieldTransformation> transIter = mapping.getFieldTransformations().iterator(); |
| while (transIter.hasNext()) { |
| FieldTransformation transformation = transIter.next(); |
| |
| if (transformation instanceof MethodBasedFieldTransformation) { |
| MethodBasedFieldTransformation methodTransformation = (MethodBasedFieldTransformation) transformation; |
| try { |
| Class<?> returnType = Helper.getDeclaredMethod(mapping.getDescriptor().getJavaClass(), methodTransformation.getMethodName(), null).getReturnType(); |
| getFieldDefFromDBField(methodTransformation.getField()).setType(returnType); |
| } catch (NoSuchMethodException ex) { |
| // For some reason, the method type could not be retrieved, |
| // use the default java.lang.String type |
| } |
| } else { |
| // Must be a TransformerBasedFieldTransformation |
| TransformerBasedFieldTransformation classTransformation = (TransformerBasedFieldTransformation) transformation; |
| String methodName = "buildFieldValue"; |
| Class<?>[] params = new Class<?>[] {Object.class, String.class, Session.class}; |
| |
| try { |
| Class<?> returnType = Helper.getDeclaredMethod(classTransformation.getTransformerClass(), methodName, params).getReturnType(); |
| |
| if (returnType.equals(Object.class)) { |
| // User needs to be more specific with their class |
| // transformer return type if they are using DDL. Throw |
| // an exception. |
| throw ValidationException.missingFieldTypeForDDLGenerationOfClassTransformation(mapping.getDescriptor(), mapping.getAttributeName(), methodName); |
| } |
| |
| getFieldDefFromDBField(classTransformation.getField()).setType(returnType); |
| } catch (NoSuchMethodException ex) { |
| // For some reason, the method type could not be retrieved. |
| // Did the interface method change? Throw an exception. |
| throw ValidationException.missingTransformerMethodForDDLGenerationOfClassTransformation(mapping.getDescriptor(), mapping.getAttributeName(), methodName); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Add the foreign key to the aggregate collection mapping target table. |
| * Also add listOrderField if specified. |
| */ |
| protected void createAggregateTargetTable(AggregateCollectionMapping mapping) { |
| TableDefinition targetTable = getTableDefFromDBTable(mapping.getReferenceDescriptor().getDefaultTable()); |
| addFieldsForMappedKeyMapContainerPolicy(mapping.getContainerPolicy(), targetTable); |
| |
| Iterator<DatabaseField> aggregateFieldIterator = mapping.getReferenceDescriptor().getFields().iterator(); |
| while (aggregateFieldIterator.hasNext()) { |
| DatabaseField dbField = aggregateFieldIterator.next(); |
| //add the target definition to the table definition |
| FieldDefinition fieldDef = getFieldDefFromDBField(dbField); |
| if (!targetTable.getFields().contains(fieldDef)) { |
| targetTable.addField(fieldDef); |
| } |
| } |
| |
| //unlike normal one-to-many mapping, aggregate collection mapping does not have 1:1 back reference |
| //mapping, so the target foreign key fields are not stored in the target descriptor. |
| List<String> fkFieldNames = new ArrayList<>(); |
| List<String> targetFieldNames = new ArrayList<>(); |
| List<DatabaseField> fkFields = mapping.getTargetForeignKeyFields(); |
| List<DatabaseField> targetFields = mapping.getSourceKeyFields(); |
| DatabaseField targetField = null; |
| for (int index = 0; index < fkFields.size(); index++) { |
| DatabaseField fkField = fkFields.get(index); |
| targetField = targetFields.get(index); |
| fkFieldNames.add(fkField.getNameDelimited(databasePlatform)); |
| targetFieldNames.add(targetField.getNameDelimited(databasePlatform)); |
| |
| fkField = resolveDatabaseField(fkField, targetField); |
| FieldDefinition fieldDef = getFieldDefFromDBField(fkField); |
| if (!targetTable.getFields().contains(fieldDef)) { |
| targetTable.addField(fieldDef); |
| } |
| } |
| |
| // add a foreign key constraint from fk field to target field |
| DatabaseTable sourceDatabaseTable = targetField.getTable(); |
| TableDefinition sourceTable = getTableDefFromDBTable(sourceDatabaseTable); |
| |
| if (mapping.getListOrderField() != null) { |
| FieldDefinition fieldDef = getFieldDefFromDBField(mapping.getListOrderField()); |
| TableDefinition table = getTableDefFromDBTable(mapping.getListOrderField().getTable()); |
| if (!table.getFields().contains(fieldDef)) { |
| table.addField(fieldDef); |
| } |
| } |
| if (mapping.getDescriptor().hasTablePerClassPolicy()) { |
| return; |
| } |
| addForeignKeyConstraint(targetTable, sourceTable, fkFieldNames, targetFieldNames, mapping.isCascadeOnDeleteSetOnDatabase()); |
| } |
| |
| protected void addForeignKeyFieldToSourceTargetTable(OneToOneMapping mapping) { |
| if (!mapping.isForeignKeyRelationship() |
| || (mapping.getReferenceDescriptor().hasTablePerClassPolicy() |
| && mapping.getReferenceDescriptor().getTablePerClassPolicy().hasChild())) { |
| return; |
| } |
| boolean cascadeDelete = false; |
| // Find mappedBy target mapping to check constraint cascade. |
| for (DatabaseField foreignKey : mapping.getSourceToTargetKeyFields().values()) { |
| DatabaseMapping mappedBy = mapping.getReferenceDescriptor().getObjectBuilder().getMappingForField(foreignKey); |
| if (mappedBy != null && mappedBy.isOneToOneMapping()) { |
| cascadeDelete = ((OneToOneMapping)mappedBy).isCascadeOnDeleteSetOnDatabase(); |
| } else { |
| List<DatabaseMapping> readOnlyMappings = mapping.getReferenceDescriptor().getObjectBuilder().getReadOnlyMappingsForField(foreignKey); |
| if (readOnlyMappings != null) { |
| for (DatabaseMapping mappedByPK : readOnlyMappings) { |
| if (mappedByPK.isOneToOneMapping()) { |
| cascadeDelete = ((OneToOneMapping)mappedByPK).isCascadeOnDeleteSetOnDatabase(); |
| if (cascadeDelete) { |
| break; |
| } |
| } |
| } |
| } |
| } |
| if (cascadeDelete) { |
| break; |
| } |
| } |
| |
| // If the mapping is optional and uses primary key join columns, don't |
| // generate foreign key constraints which would require the target to |
| // always be set. |
| if (! mapping.isOptional() || ! mapping.isOneToOnePrimaryKeyRelationship()) { |
| addForeignMappingFkConstraint(mapping.getSourceToTargetKeyFields(), cascadeDelete); |
| } |
| } |
| |
| protected void addForeignKeyFieldToSourceTargetTable(OneToManyMapping mapping) { |
| if (mapping.getDescriptor().hasTablePerClassPolicy()) { |
| return; |
| } |
| addForeignMappingFkConstraint(mapping.getTargetForeignKeysToSourceKeys(), mapping.isCascadeOnDeleteSetOnDatabase()); |
| if (mapping.getListOrderField() != null) { |
| FieldDefinition fieldDef = getFieldDefFromDBField(mapping.getListOrderField()); |
| TableDefinition table = getTableDefFromDBTable(mapping.getListOrderField().getTable()); |
| if (!table.getFields().contains(fieldDef)) { |
| table.addField(fieldDef); |
| } |
| } |
| } |
| |
| protected void addForeignMappingFkConstraint(final Map<DatabaseField, DatabaseField> srcFields, boolean cascadeOnDelete) { |
| // srcFields map from the foreign key field to the target key field |
| |
| if(srcFields.size() == 0) { |
| return; |
| } |
| |
| List<DatabaseField> fkFields = new ArrayList<>(); |
| List<DatabaseField> targetFields = new ArrayList<>(); |
| |
| for (Map.Entry<DatabaseField, DatabaseField> field : srcFields.entrySet()) { |
| fkFields.add(field.getKey()); |
| targetFields.add(field.getValue()); |
| } |
| addJoinColumnsFkConstraint(fkFields, targetFields, cascadeOnDelete); |
| } |
| |
| /** |
| * Build a table definition object from a database table object |
| */ |
| protected TableDefinition getTableDefFromDBTable(DatabaseTable databaseTable) { |
| TableDefinition tableDefinition = this.tableMap.get(databaseTable.getName()); |
| |
| if (tableDefinition == null) { |
| //table not built yet, simply built it |
| tableDefinition = new TableDefinition(); |
| tableDefinition.setTable(databaseTable); |
| tableDefinition.setName(databaseTable.getNameDelimited(databasePlatform)); |
| tableDefinition.setQualifier(databaseTable.getTableQualifier()); |
| if (databaseTable.hasUniqueConstraints()) { |
| addUniqueKeyConstraints(tableDefinition, databaseTable.getUniqueConstraints()); |
| } |
| if (databaseTable.hasIndexes()) { |
| tableDefinition.getIndexes().addAll(databaseTable.getIndexes()); |
| } |
| if (databaseTable.getCreationSuffix() !=null){ |
| tableDefinition.setCreationSuffix(databaseTable.getCreationSuffix()); |
| } |
| |
| // Add the foreign key constraints that were set on the table. |
| if (databaseTable.hasForeignKeyConstraints()) { |
| tableDefinition.setUserDefinedForeignKeyConstraints(databaseTable.getForeignKeyConstraints()); |
| } |
| |
| tableMap.put(databaseTable.getName(), tableDefinition); |
| } |
| |
| return tableDefinition; |
| } |
| |
| /** |
| * Resolve the foreign key database field metadata in relation table or direct collection/map table. |
| * Those metadata includes type, and maybe dbtype/size/subsize if DatabaseField carries those info. |
| */ |
| protected DatabaseField resolveDatabaseField(DatabaseField childField, DatabaseField parentField) { |
| //set through the type from the source table key field to the relation or direct collection table key field. |
| DatabaseField resolvedDatabaseField = new DatabaseField(); |
| // find original field in the parent table, which contains actual type definitions |
| // if 'resolvedParentField' is null, there is no corresponding field definition (typo?) |
| DatabaseField resolvedParentField = databaseFields.get(parentField); |
| |
| resolvedDatabaseField.setName(childField.getName()); |
| //Table should be set, otherwise other same name field will be used wrongly because equals() is true. |
| //Fix for GF#1392 the same name column for the entity and many-to-many table cause wrong pk constraint. |
| resolvedDatabaseField.setTable(childField.getTable()); |
| |
| // type definitions from parent field definition |
| if(resolvedParentField != null) { |
| resolvedDatabaseField.setType(resolvedParentField.getType()); |
| resolvedDatabaseField.setScale(resolvedParentField.getScale()); |
| resolvedDatabaseField.setLength(resolvedParentField.getLength()); |
| resolvedDatabaseField.setPrecision(resolvedParentField.getPrecision()); |
| } |
| |
| // these are defined in childField definition(see @JoinColumn) |
| resolvedDatabaseField.setUnique(childField.isUnique()); |
| resolvedDatabaseField.setNullable(childField.isNullable()); |
| resolvedDatabaseField.setUpdatable(childField.isUpdatable()); |
| resolvedDatabaseField.setInsertable(childField.isInsertable()); |
| resolvedDatabaseField.setUseDelimiters(childField.shouldUseDelimiters()); |
| resolvedDatabaseField.useUpperCaseForComparisons(childField.getUseUpperCaseForComparisons()); |
| resolvedDatabaseField.setNameForComparisons(childField.getNameForComparisons()); |
| |
| String columnDef = childField.getColumnDefinition(); |
| if(columnDef == null || columnDef.trim().equals("")) { |
| // if childField has no column definition, follow the definition of the parent field |
| if(resolvedParentField != null) { |
| resolvedDatabaseField.setColumnDefinition(resolvedParentField.getColumnDefinition()); |
| } |
| } else { |
| resolvedDatabaseField.setColumnDefinition(columnDef); |
| } |
| |
| return resolvedDatabaseField; |
| } |
| |
| /** |
| * Build a field definition object from a database field. |
| */ |
| protected FieldDefinition getFieldDefFromDBField(DatabaseField dbField) { |
| FieldDefinition fieldDef = this.fieldMap.get(dbField); |
| |
| if (fieldDef == null) { |
| //not built yet, build one |
| fieldDef = new FieldDefinition(); |
| fieldDef.setName(dbField.getNameDelimited(databasePlatform)); |
| //added for extending tables where the field needs to be looked up |
| fieldDef.setDatabaseField(dbField); |
| |
| if (dbField.getColumnDefinition() != null && dbField.getColumnDefinition().length() > 0) { |
| // This column definition would include the complete definition of the |
| // column like type, size, "NULL/NOT NULL" clause, unique key clause |
| fieldDef.setTypeDefinition(dbField.getColumnDefinition()); |
| } else { |
| Class<?> fieldType = dbField.getType(); |
| FieldTypeDefinition fieldTypeDef = (fieldType == null) ? null : databasePlatform.getFieldTypeDefinition(fieldType); |
| |
| // Check if the user field is a String and only then allow the length specified |
| // in the @Column annotation to be set on the field. |
| if (fieldType != null) { |
| // If a length has been specified, set it, otherwise let the |
| // field def from individual platforms handle it. |
| if (dbField.getLength() > 0) { |
| fieldDef.setSize(dbField.getLength()); |
| } else if (dbField.getPrecision() > 0) { |
| fieldDef.setSize(dbField.getPrecision()); |
| fieldDef.setSubSize(dbField.getScale()); |
| } |
| } |
| |
| if ((fieldType == null) || (!fieldType.isPrimitive() && (fieldTypeDef == null))) { |
| //TODO: log a warning for inaccessible type or not convertable type. |
| AbstractSessionLog.getLog().log(SessionLog.CONFIG, SessionLog.METADATA, "field_type_set_to_java_lang_string", dbField.getQualifiedName(), fieldType); |
| |
| //set the default type (lang.String) to all un-resolved java type, like null, Number, util.Date, NChar/NType, Calendar |
| //sql.Blob/Clob, Object, or unknown type). Please refer to bug 4352820. |
| fieldDef.setType(ClassConstants.STRING); |
| } else { |
| //need to convert the primitive type if applied. |
| fieldDef.setType(ConversionManager.getObjectClass(fieldType)); |
| } |
| |
| fieldDef.setShouldAllowNull(dbField.isNullable()); |
| fieldDef.setUnique(dbField.isUnique()); |
| } |
| this.fieldMap.put(dbField, fieldDef); |
| this.databaseFields.put(dbField, dbField); |
| } |
| |
| return fieldDef; |
| } |
| |
| /** |
| * Build and add a field definition object to relation table |
| */ |
| protected void setFieldToRelationTable(DatabaseField dbField, TableDefinition table) { |
| FieldDefinition fieldDef = getFieldDefFromDBField(dbField); |
| |
| if (!table.getFields().contains(fieldDef)) { |
| //only add the field once, to avoid add twice if m:m is bi-directional. |
| table.addField(getFieldDefFromDBField(dbField)); |
| fieldDef.setIsPrimaryKey(true); // make this a PK as we will be creating constrains later |
| } |
| } |
| |
| protected void processAdditionalTablePkFields(ClassDescriptor descriptor) { |
| // only if there are additional tables |
| if (!descriptor.hasMultipleTables()) { |
| return; |
| } |
| |
| DatabaseTable databaseTable = null; |
| Iterator<DatabaseTable> dbTblIter = descriptor.getTables().iterator(); |
| while (dbTblIter.hasNext()) { |
| databaseTable = dbTblIter.next(); |
| Map<DatabaseField, DatabaseField> srcFields = descriptor.getAdditionalTablePrimaryKeyFields().get(databaseTable); |
| if ((null != srcFields) && srcFields.size() > 0) { |
| // srcFields is from the secondary field to the primary key field |
| // Let's make fk constraint from the secondary field to the primary key field |
| List<DatabaseField> fkFields = new ArrayList<>(); |
| List<DatabaseField> pkFields = new ArrayList<>(); |
| |
| for (Map.Entry<DatabaseField, DatabaseField> field : srcFields.entrySet()) { |
| pkFields.add(field.getKey()); |
| fkFields.add(field.getValue()); |
| } |
| addJoinColumnsFkConstraint(fkFields, pkFields, descriptor.isCascadeOnDeleteSetOnDatabaseOnSecondaryTables()); |
| } |
| } |
| } |
| |
| protected void addJoinColumnsFkConstraint(List<DatabaseField> fkFields, List<DatabaseField> targetFields, boolean cascadeOnDelete) { |
| assert fkFields.size() == targetFields.size(); |
| |
| if (fkFields.size() == 0) { |
| return; |
| } |
| |
| DatabaseField fkField = null; |
| DatabaseField targetField = null; |
| List<String> fkFieldNames = new ArrayList<>(); |
| List<String> targetFieldNames = new ArrayList<>(); |
| |
| DatabaseTable sourceTable = fkFields.get(0).getTable(); |
| TableDefinition sourceTableDef = getTableDefFromDBTable(sourceTable); |
| |
| for (int i=0; i < fkFields.size(); i++) { |
| fkField = fkFields.get(i); |
| targetField = targetFields.get(i); |
| fkFieldNames.add(fkField.getNameDelimited(this.databasePlatform)); |
| targetFieldNames.add(targetField.getNameDelimited(this.databasePlatform)); |
| |
| FieldDefinition fkFieldDef = fieldMap.get(fkField); |
| FieldDefinition targetFieldDef = fieldMap.get(targetField); |
| |
| if (targetFieldDef != null) { |
| // UnidirectionalOneToOneMapping case |
| if (fkFieldDef == null) { |
| fkFieldDef = getFieldDefFromDBField(fkField); |
| if (!sourceTableDef.getFields().contains(fkFieldDef)) { |
| sourceTableDef.addField(fkFieldDef); |
| } |
| } |
| |
| // Set the fkFieldDef type definition to the that of the target if one is not set. |
| if (fkFieldDef.getTypeDefinition() == null || fkFieldDef.getTypeDefinition().trim().equals("")) { |
| fkFieldDef.setTypeDefinition(targetFieldDef.getTypeDefinition()); |
| } |
| |
| // Also ensure that the type, size and subsize of the foreign key field is |
| // same as that of the original field. |
| fkFieldDef.setType(targetFieldDef.getType()); |
| fkFieldDef.setSize(targetFieldDef.getSize()); |
| fkFieldDef.setSubSize(targetFieldDef.getSubSize()); |
| } |
| } |
| |
| // add a foreign key constraint |
| DatabaseTable targetTable = targetField.getTable(); |
| TableDefinition targetTableDef = getTableDefFromDBTable(targetTable); |
| |
| addForeignKeyConstraint(sourceTableDef, targetTableDef, fkFieldNames, targetFieldNames, cascadeOnDelete); |
| } |
| |
| /** |
| * Add a foreign key constraint to the source table. |
| */ |
| protected void addForeignKeyConstraint(TableDefinition sourceTableDef, TableDefinition targetTableDef, |
| List<String> fkFields, List<String> targetFields, boolean cascadeOnDelete) { |
| |
| // Only generate FK constraints if instructed to |
| if (! this.generateFKConstraints){ |
| return; |
| } |
| assert fkFields.size() > 0 && fkFields.size() == targetFields.size(); |
| |
| // target keys could be primary keys or candidate(unique) keys of the target table |
| |
| List<String> fkFieldNames = fkFields; |
| List<String> targetFieldNames = targetFields; |
| |
| if (fkFields.size() > 1) { |
| // if composite key, we should consider the order of keys. |
| // Foreign Key constraint should follow the primary/unique key order of the target table. |
| // e.g. if the primary key constraint of the target table is (p2, p1), |
| // foreign key constraint should be "(f2, f1) REFERENCES TARGET (p2, p1)". |
| |
| // we try to reorder keys using primary keys or unique keys order of the target table, |
| // but if we might not resolve it due to incorrect field name, then let it as it is. |
| // This will trigger underlying database exception so users can recognize errors. |
| |
| boolean resolved = false; |
| boolean error = false; |
| |
| Map<String, String> targetToFkField = new LinkedHashMap<>(); |
| for (int index = 0; index < fkFields.size(); index++) { |
| String targetField = targetFields.get(index); |
| if (targetToFkField.containsKey(targetField)) { |
| //target key column appears more than once |
| error = true; |
| break; |
| } |
| targetToFkField.put(targetField, fkFields.get(index)); |
| } |
| |
| List<String> orderedFkFields = new ArrayList<>(fkFields.size()); |
| List<String> orderedTargetFields = new ArrayList<>(targetFields.size()); |
| |
| if (!error) { |
| // if target fields are primary keys |
| resolved = true; |
| for (String pkField : targetTableDef.getPrimaryKeyFieldNames()) { |
| String fkField = targetToFkField.get(pkField); |
| if (fkField == null) { |
| //primary key column not found |
| resolved = false; |
| break; |
| } |
| orderedFkFields.add(fkField); |
| orderedTargetFields.add(pkField); |
| } |
| } |
| |
| if (!error && !resolved) { |
| // if target fields are unique keys |
| for (UniqueKeyConstraint uniqueConstraint : targetTableDef.getUniqueKeys()) { |
| orderedFkFields.clear(); |
| orderedTargetFields.clear(); |
| |
| resolved = true; |
| for (String ukField : uniqueConstraint.getSourceFields()) { |
| String fkField = targetToFkField.get(ukField); |
| if (fkField == null) { |
| //unique key column not found |
| resolved = false; |
| break; |
| } |
| orderedFkFields.add(fkField); |
| orderedTargetFields.add(ukField); |
| } |
| if (resolved) { |
| break; |
| } |
| } |
| } |
| |
| if (resolved) { |
| fkFieldNames = orderedFkFields; |
| targetFieldNames = orderedTargetFields; |
| } |
| } |
| |
| // For bidirectional relationships both side of mapping will make the same FK constraint twice. |
| // TableDefinition.addForeignKeyConstraint() will ignore the same FK constraint. |
| |
| ForeignKeyConstraint constraint = sourceTableDef.buildForeignKeyConstraint(fkFieldNames, targetFieldNames, |
| targetTableDef, this.databasePlatform); |
| constraint.setShouldCascadeOnDelete(cascadeOnDelete); |
| sourceTableDef.addForeignKeyConstraint(constraint); |
| } |
| |
| protected void addUniqueKeyConstraints(TableDefinition sourceTableDef, Map<String, List<List<String>>> uniqueConstraintsMap) { |
| int serialNumber = -1; |
| |
| for (String name : uniqueConstraintsMap.keySet()) { |
| List<List<String>> uniqueConstraints = uniqueConstraintsMap.get(name); |
| |
| for (List<String> uniqueConstraint : uniqueConstraints) { |
| if (uniqueConstraint != null) { |
| // To keep the serialNumber consecutive, increment it only |
| // if the name is not specified. |
| if (name == null || name.equals("")) { |
| serialNumber++; |
| } |
| |
| sourceTableDef.addUniqueKeyConstraint(sourceTableDef.buildUniqueKeyConstraint(name, uniqueConstraint, serialNumber, databasePlatform)); |
| } |
| } |
| } |
| } |
| } |