blob: 0118de9bac6e96156d67fc70a38e5fd23ee59686 [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. 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
// Dies Koper - add support for creating indices on tables
// 01/11/2013-2.5 Guy Pelletier
// - 389090: JPA 2.1 DDL Generation Support
// 02/04/2013-2.5 Guy Pelletier
// - 389090: JPA 2.1 DDL Generation Support
// 04/12/2013-2.5 Guy Pelletier
// - 405640: JPA 2.1 schema generation drop operation fails to include dropping defaulted fk constraints.
package org.eclipse.persistence.tools.schemaframework;
import java.io.Writer;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.Vector;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.exceptions.EclipseLinkException;
import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.internal.sequencing.Sequencing;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.sessions.DatabaseSessionImpl;
import org.eclipse.persistence.sequencing.DefaultSequence;
import org.eclipse.persistence.sequencing.NativeSequence;
import org.eclipse.persistence.sequencing.Sequence;
import org.eclipse.persistence.sequencing.TableSequence;
import org.eclipse.persistence.sequencing.UnaryTableSequence;
/**
* <p>
* <b>Purpose</b>: Define all user level protocol for development time database manipulation.
* <p>
* <b>Responsibilities</b>:
* <ul>
* <li> Define protocol for schema creation.
* <li> Define any useful testing specific protocol.
* </ul>
*/
public class SchemaManager {
protected DatabaseSessionImpl session;
protected Writer createSchemaWriter;
protected Writer dropSchemaWriter;
protected boolean createSQLFiles = true; //if true, schema writer will add terminator string.
protected TableCreator defaultTableCreator;
/** Allow table creator to occur "fast" by just deleting all the rows. */
public static boolean FAST_TABLE_CREATOR = false;
/** Allow replacing of table to force the drop, this may require several passes. */
public static boolean FORCE_DROP = true;
/** Flag to determine if database schemas should be created during DDL generation */
protected boolean createDatabaseSchemas = false;
protected HashSet<String> createdDatabaseSchemas = new HashSet<>();
protected HashSet<String> createdDatabaseSchemasOnDatabase = new HashSet<>();
protected HashMap<String, DatabaseObjectDefinition> dropDatabaseSchemas = new HashMap<>();
public SchemaManager(DatabaseSessionImpl session) {
this.session = session;
}
public SchemaManager(org.eclipse.persistence.sessions.DatabaseSession session) {
this.session = ((DatabaseSessionImpl)session);
}
protected Writer getDropSchemaWriter() {
return (dropSchemaWriter == null) ? createSchemaWriter : dropSchemaWriter;
}
/**
* PUBLIC: If the schema manager is writing to a writer, append this string
* to that writer.
*/
public void appendToDDLWriter(String stringToWrite) {
// If this method is called, we know that it is the old case and
// it would not matter which schemaWriter we use as both the
// create and drop schemaWriters are essentially the same.
// So just pick one.
appendToDDLWriter(createSchemaWriter, stringToWrite);
}
public void appendToDDLWriter(Writer schemaWriter, String stringToWrite) {
if (schemaWriter == null) {
return; //do nothing. Ignore append request
}
try {
schemaWriter.write(stringToWrite);
schemaWriter.flush();
} catch (java.io.IOException ioException) {
throw ValidationException.fileError(ioException);
}
}
/**
* INTERNAL:
* builds the field names based on the type read in from the builder
*/
public void buildFieldTypes(TableDefinition tableDef) {
tableDef.buildFieldTypes(getSession());
}
/**
* PUBLIC:
* Close the schema writer.
*/
public void closeDDLWriter() {
closeDDLWriter(createSchemaWriter);
closeDDLWriter(dropSchemaWriter);
createSchemaWriter = null;
dropSchemaWriter = null;
}
public void closeDDLWriter(Writer schemaWriter) {
if (schemaWriter == null) {
return;
}
try {
schemaWriter.flush();
schemaWriter.close();
} catch (java.io.IOException ioException) {
throw ValidationException.fileError(ioException);
}
}
/**
* INTERNAL:
* Called when dropping tables. Will build a map of those schemas (and a
* table that references it) that should be dropped.
*/
protected void collectDatabaseSchemasForDrop(DatabaseObjectDefinition databaseObjectDefinition) {
if (createDatabaseSchemas && databaseObjectDefinition.hasDatabaseSchema()) {
if (! dropDatabaseSchemas.containsKey(databaseObjectDefinition.getDatabaseSchema())) {
dropDatabaseSchemas.put(databaseObjectDefinition.getDatabaseSchema(), databaseObjectDefinition);
}
}
}
/**
* Use the table definition to add the constraints to the database, this is normally done
* in two steps to avoid dependencies.
*/
public void createConstraints(TableDefinition tableDefinition) throws EclipseLinkException {
boolean usesBatchWriting = false;
if (getSession().getPlatform().usesBatchWriting()) {
usesBatchWriting = true;
getSession().getPlatform().setUsesBatchWriting(false);
}
try {
if (shouldWriteToDatabase()) {
tableDefinition.createConstraintsOnDatabase(getSession());
} else {
tableDefinition.setCreateSQLFiles(createSQLFiles);
tableDefinition.createConstraints(getSession(), createSchemaWriter);
}
} finally {
if (usesBatchWriting) {
getSession().getPlatform().setUsesBatchWriting(true);
}
}
}
void createUniqueConstraints(TableDefinition tableDefinition) throws EclipseLinkException {
if (shouldWriteToDatabase()) {
tableDefinition.createUniqueConstraintsOnDatabase(getSession());
} else {
tableDefinition.setCreateSQLFiles(createSQLFiles);
tableDefinition.createUniqueConstraints(getSession(), createSchemaWriter);
}
}
void createForeignConstraints(TableDefinition tableDefinition) throws EclipseLinkException {
if (shouldWriteToDatabase()) {
tableDefinition.createForeignConstraintsOnDatabase(getSession());
} else {
tableDefinition.setCreateSQLFiles(createSQLFiles);
tableDefinition.createForeignConstraints(getSession(), createSchemaWriter);
}
}
/**
* Use the definition object to create the schema entity on the database.
* This is used for creating tables, views, procedures ... etc ...
*/
public void createObject(DatabaseObjectDefinition databaseObjectDefinition) throws EclipseLinkException {
boolean usesBatchWriting = false;
if (getSession().getPlatform().usesBatchWriting()) {
usesBatchWriting = true;
getSession().getPlatform().setUsesBatchWriting(false);
}
try {
if (shouldWriteToDatabase()) {
// Check if we should create a database schema for this
// database object definition on the database. It is only
// create once and for the first database object definition
// that references it.
if (shouldCreateDatabaseSchema(databaseObjectDefinition, createdDatabaseSchemasOnDatabase)) {
databaseObjectDefinition.createDatabaseSchemaOnDatabase(getSession(), createdDatabaseSchemasOnDatabase);
}
databaseObjectDefinition.createOnDatabase(getSession());
} else {
// Check if we should create a database schema for this
// database object definition on the database. It is only
// create once and for the first database object definition
// that references it.
if (shouldCreateDatabaseSchema(databaseObjectDefinition, createdDatabaseSchemas)) {
databaseObjectDefinition.createDatabaseSchema(getSession(), createSchemaWriter, createdDatabaseSchemas);
appendToDDLWriter(createSchemaWriter, "\n");
}
databaseObjectDefinition.createObject(getSession(), createSchemaWriter);
if (createSQLFiles){
this.appendToDDLWriter(createSchemaWriter, getSession().getPlatform().getStoredProcedureTerminationToken());
}
this.appendToDDLWriter(createSchemaWriter, "\n");
}
databaseObjectDefinition.postCreateObject(getSession(), createSchemaWriter, createSQLFiles);
} finally {
if (usesBatchWriting) {
getSession().getPlatform().setUsesBatchWriting(true);
}
}
}
/**
* Create all the receiver's sequences on the database for all of the loaded descriptors.
*/
public void createSequences() throws EclipseLinkException {
createOrReplaceSequences(true);
}
/**
* INTERNAL:
* Set to true if database schemas should be built during the DDL generation.
*/
public void setCreateDatabaseSchemas(boolean createDatabaseSchemas) {
this.createDatabaseSchemas = createDatabaseSchemas;
}
public void setCreateSQLFiles(boolean genFlag) {
this.createSQLFiles = genFlag;
}
/**
* Drop and recreate all the receiver's sequences on the database for all of the loaded descriptors.
*/
public void replaceSequences() throws EclipseLinkException {
createOrReplaceSequences(false);
}
/**
* Common implementor for createSequence and replaceSequence
* @param create - true to create the sequences, false to replace them (dropped then create)
*/
protected void createOrReplaceSequences(boolean create) throws EclipseLinkException {
createOrReplaceSequences(create, create);
}
/**
* Common implementor for createSequence and replaceSequence, distinguishes between sequence tables and sequence objects
* @param createSequenceTables - true to create the sequences tables, false to replace them (dropped then create)
* @param createSequences - true to create the sequences objects, false to replace them (dropped then create)
*/
protected void createOrReplaceSequences(boolean createSequenceTables, boolean createSequences) throws EclipseLinkException {
// PERF: Allow a special "fast" flag to be set on the session causes a delete from the table instead of a replace.
boolean fast = FAST_TABLE_CREATOR;
if (fast) {
// Assume sequences already created.
return;
}
processSequenceDefinitions(createSequenceTables, createSequences, true);
}
/**
* This will drop the database schemas if managing the database schemas.
*/
protected void dropSequences() {
processSequenceDefinitions(false, false, false);
}
/**
* Method creates database tables/objects. If create is true, it will
* attempt to create the object and silently ignore exceptions. If create
* is false, it will drop the object ignoring any exceptions, then create
* it if the replace flag is true (otherwise a drop only).
*
* @param definition - the sequence definition
* @param createTables - true if table sequence table definitions should be created.
* @param createSequences - true if the sequence definition should be created,
* false if it should be dropped.
* @param replace - true if table definitions and sequence definitions should be replaced.
*/
protected void processSequenceDefinition(SequenceDefinition definition, final boolean createTables, final boolean createSequences, final boolean replace, HashSet<String> createdTableNames, HashSet<String> droppedTableNames) throws EclipseLinkException {
try {
// Handle the table definitions first.
if (definition.isTableSequenceDefinition()) {
TableDefinition tableDefinition = definition.buildTableDefinition();
// Check that we haven't already created the table.
if (! createdTableNames.contains(tableDefinition.getFullName())) {
createdTableNames.add(tableDefinition.getFullName());
// Check if it exists on the database. NOTE: when writing to scripts only with
// no connection, this of course will always return false hence the need for
// the createdSequenceTableNames collection above.
boolean exists = checkTableExists(tableDefinition);
if (createTables) {
// Don't create it if it already exists on the database.
// In all all other cases, write it out.
if ((shouldWriteToDatabase() && ! exists) || ! shouldWriteToDatabase()) {
createObject(tableDefinition);
}
} else {
// Don't check exists since if writing to scripts only with no connection,
// we'll never write the sql out. When executing to the database, the drop
// will fail and we'll ignore it. Note: TableSequenceDefinition's will drop
// their table definitions as needed (i.e.) when the jpa create database
// schemas flag is set and the table definition has a schema. Otherwise,
// we should not drop sequence tables since they may be re-used across
// persistence units (default behavior right now).
// TODO: We should drop them really unless it is the default SEQUENCE table??
if (replace) {
dropObject(tableDefinition);
createObject(tableDefinition);
}
}
}
}
} catch (DatabaseException exception) {
// ignore any database exceptions here and keep going.
}
// Handle the sequence objects second.
try {
if (createSequences) {
createObject(definition);
} else {
try {
// If the sequence definition has and will drop a table definition, then check
// if we have already dropped it. Table definitions are dropped as a whole if
// they have a schema name and the jpa create database schemas flag is set to true.
if (definition.isTableSequenceDefinition()) {
if (((TableSequenceDefinition) definition).shouldDropTableDefinition()) {
String tableDefinitionTableName = ((TableSequenceDefinition) definition).getSequenceTableName();
// If we have already dropped it, move on, otherwise drop it!
if (droppedTableNames.contains(tableDefinitionTableName)) {
return; // cut out early, we've already seen this table.
} else {
droppedTableNames.add(tableDefinitionTableName);
}
}
}
dropObject(definition);
} catch (DatabaseException exception) {
// Ignore table not found for first creation
}
// Drop only scripts we don't want to replace.
if (replace) {
createObject(definition);
}
}
} catch (Exception exception) {
// ignore any database exceptions here and keep chugging
}
}
/**
* Common implementor for createSequence and replaceSequence, distinguishes between sequence tables and sequence objects
* @param createSequenceTables - true to create the sequences tables, false to replace them (dropped then create)
* @param createSequences - true to create the sequences objects, false to replace them (dropped then create)
* @param replaceSequences - true to actually replace, false to drop only.
*/
protected void processSequenceDefinitions(boolean createSequenceTables, boolean createSequences, boolean replaceSequences) throws EclipseLinkException {
Sequencing sequencing = getSession().getSequencing();
// Not required on Sybase native etc.
if (sequencing != null && sequencing.whenShouldAcquireValueForAll() != Sequencing.AFTER_INSERT) {
// Build the sequence definitions.
HashSet<SequenceDefinition> sequenceDefinitions = buildSequenceDefinitions();
// Now process the sequence definitions.
// CR 3870467, do not log stack
boolean shouldLogExceptionStackTrace = session.getSessionLog().shouldLogExceptionStackTrace();
session.getSessionLog().setShouldLogExceptionStackTrace(false);
HashSet<String> createdSequenceTableNames = new HashSet();
HashSet<String> droppedSequenceTableNames = new HashSet();
for (SequenceDefinition sequenceDefinition : sequenceDefinitions) {
processSequenceDefinition(sequenceDefinition, createSequenceTables, createSequences, replaceSequences, createdSequenceTableNames, droppedSequenceTableNames);
}
// Set the log stack trace flag back.
session.getSessionLog().setShouldLogExceptionStackTrace(shouldLogExceptionStackTrace);
}
}
/**
* INTERNAL:
* Build the sequence definitions.
*/
protected HashSet<SequenceDefinition> buildSequenceDefinitions() {
// Remember the processed - to handle each sequence just once.
HashSet processedSequenceNames = new HashSet();
HashSet<SequenceDefinition> sequenceDefinitions = new HashSet<>();
for (ClassDescriptor descriptor : getSession().getDescriptors().values()) {
if (descriptor.usesSequenceNumbers()) {
String seqName = descriptor.getSequenceNumberName();
if (seqName == null) {
seqName = getSession().getDatasourcePlatform().getDefaultSequence().getName();
}
if (! processedSequenceNames.contains(seqName)) {
processedSequenceNames.add(seqName);
Sequence sequence = getSession().getDatasourcePlatform().getSequence(seqName);
SequenceDefinition sequenceDefinition = buildSequenceDefinition(sequence);
if (sequenceDefinition != null) {
sequenceDefinitions.add(sequenceDefinition);
}
}
}
}
return sequenceDefinitions;
}
/**
* Check if the table exists by issuing a query.
* @param table database table meta-data
* @param suppressLogging whether to suppress logging during query execution
* @return value of {@code true} if given table exists or {@code false} otherwise
*/
public boolean checkTableExists(TableDefinition table, final boolean suppressLogging) {
final boolean loggingOff = session.isLoggingOff();
try {
return session.getPlatform().checkTableExists(session, table, suppressLogging);
} finally {
session.setLoggingOff(loggingOff);
}
}
/**
* Check if the table exists by issuing a query.
* Logging is suppressed during query execution.
* @param table database table meta-data
* @return value of {@code true} if given table exists or {@code false} otherwise
*/
public boolean checkTableExists(TableDefinition table) {
return checkTableExists(table, true);
}
protected SequenceDefinition buildSequenceDefinition(Sequence sequence) {
if (sequence.shouldAcquireValueAfterInsert()) {
return null;
}
if (sequence instanceof TableSequence ||
(sequence instanceof DefaultSequence && ((DefaultSequence)sequence).getDefaultSequence() instanceof TableSequence)) {
return new TableSequenceDefinition(sequence, createDatabaseSchemas);
} else if (sequence instanceof UnaryTableSequence ||
(sequence instanceof DefaultSequence && ((DefaultSequence)sequence).getDefaultSequence() instanceof UnaryTableSequence)) {
return new UnaryTableSequenceDefinition(sequence, createDatabaseSchemas);
} else if (sequence instanceof NativeSequence ||
(sequence instanceof DefaultSequence && ((DefaultSequence)sequence).getDefaultSequence() instanceof NativeSequence)) {
NativeSequence nativeSequence = null;
if (sequence instanceof NativeSequence) {
nativeSequence = (NativeSequence)sequence;
} else {
nativeSequence = (NativeSequence)((DefaultSequence)sequence).getDefaultSequence();
}
if (nativeSequence.hasDelegateSequence()) {
return buildSequenceDefinition(((NativeSequence)sequence).getDelegateSequence());
}
return new SequenceObjectDefinition(sequence);
} else {
return null;
}
}
/**
* Use the table definition to drop the constraints from the table, this is normally done
* in two steps to avoid dependencies.
*/
public void dropConstraints(TableDefinition tableDefinition) throws EclipseLinkException {
boolean usesBatchWriting = false;
if (getSession().getPlatform().usesBatchWriting()) {
usesBatchWriting = true;
getSession().getPlatform().setUsesBatchWriting(false);
}
try {
if (shouldWriteToDatabase()) {
tableDefinition.dropConstraintsOnDatabase(getSession());
} else {
tableDefinition.setCreateSQLFiles(createSQLFiles);
tableDefinition.dropConstraints(getSession(), getDropSchemaWriter());
}
} finally {
if (usesBatchWriting) {
getSession().getPlatform().setUsesBatchWriting(true);
}
}
}
/**
* Use the definition object to drop the schema entity from the database.
* This is used for dropping tables, views, procedures ... etc ...
*/
public void dropObject(DatabaseObjectDefinition databaseObjectDefinition) throws EclipseLinkException {
boolean usesBatchWriting = false;
if (getSession().getPlatform().usesBatchWriting()) {
usesBatchWriting = true;
getSession().getPlatform().setUsesBatchWriting(false);
}
try {
// If the object definition has a database schema collect it.
collectDatabaseSchemasForDrop(databaseObjectDefinition);
databaseObjectDefinition.preDropObject(getSession(), getDropSchemaWriter(), this.createSQLFiles);
if (shouldWriteToDatabase()) {
// drop actual object
databaseObjectDefinition.dropFromDatabase(getSession());
} else {
Writer dropSchemaWriter = getDropSchemaWriter();
// drop actual object
databaseObjectDefinition.dropObject(getSession(), dropSchemaWriter, createSQLFiles);
if (this.createSQLFiles){
this.appendToDDLWriter(dropSchemaWriter, getSession().getPlatform().getStoredProcedureTerminationToken());
}
this.appendToDDLWriter(dropSchemaWriter, "\n");
}
} finally {
if (usesBatchWriting) {
getSession().getPlatform().setUsesBatchWriting(true);
}
}
}
/**
* Drop (delete) the table named tableName from the database.
*/
public void dropTable(String tableName) throws EclipseLinkException {
TableDefinition tableDefinition;
tableDefinition = new TableDefinition();
tableDefinition.setName(tableName);
dropObject(tableDefinition);
}
/**
* INTERNAL:
* Close the schema writer when the schema manger is garbage collected
*/
@Override
public void finalize() {
try {
this.closeDDLWriter();
} catch (ValidationException exception) {
// do nothing
}
}
/**
* PUBLIC:
* Use this method to generate stored procedures based on the dynamic SQL generated
* for your mappings and descriptors. This should be used with caution as it maintenance
* will be high. Stored procedures may be generated either directly on the database
* or to a file.
*/
public void generateStoredProcedures() throws EclipseLinkException {
new StoredProcedureGenerator(this).generateStoredProcedures();
}
/**
* PUBLIC:
* Use this method to generate stored procedures based on the dynamic SQL generated
* for your mappings and descriptors. This should be used with caution as it maintenance
* will be high. Stored procedures may be generated either directly on the database
* or to a file.
*/
public void generateStoredProcedures(Writer writer) throws EclipseLinkException {
new StoredProcedureGenerator(this).generateStoredProcedures(writer);
}
/**
* PUBLIC:
* Use this method to generate stored procedures based on the dynamic SQL generated
* for your mappings and descriptors. This should be used with caution as it maintenance
* will be high. Stored procedures may be generated either directly on the database
* or to a file.
*/
public void generateStoredProceduresAndAmendmentClass(Writer writer, String fullyQualifiedClassName) throws EclipseLinkException {
String className = fullyQualifiedClassName.substring(fullyQualifiedClassName.lastIndexOf('.') + 1);
String packageName = fullyQualifiedClassName.substring(0, fullyQualifiedClassName.lastIndexOf('.'));
StoredProcedureGenerator storedProcedureGenerator = new StoredProcedureGenerator(this);
storedProcedureGenerator.generateStoredProcedures();
storedProcedureGenerator.generateAmendmentClass(writer, packageName, className);
}
/**
* PUBLIC:
* Use this method to generate stored procedures based on the dynamic SQL generated
* for your mappings and descriptors. This should be used with caution as it maintenance
* will be high. Stored procedures may be generated either directly on the database
* or to a file.
*/
public void generateStoredProceduresAndAmendmentClass(String path, String fullyQualifiedClassName) throws EclipseLinkException {
java.io.FileWriter fileWriter = null;
try {
StoredProcedureGenerator storedProcedureGenerator = new StoredProcedureGenerator(this);
if (!(path.endsWith("\\") || path.endsWith("/"))) {
path = path + "\\";
}
String className = fullyQualifiedClassName.substring(fullyQualifiedClassName.lastIndexOf('.') + 1);
String packageName = fullyQualifiedClassName.substring(0, fullyQualifiedClassName.lastIndexOf('.'));
String fileName = path + className + ".java";
fileWriter = new java.io.FileWriter(fileName);
storedProcedureGenerator.generateStoredProcedures();
storedProcedureGenerator.generateAmendmentClass(fileWriter, packageName, className);
fileWriter.close();
} catch (java.io.IOException ioException) {
throw ValidationException.fileError(ioException);
} finally {
Helper.close(fileWriter);
}
}
/**
* Return the appropriate accessor.
* Assume we are dealing with a JDBC accessor.
*/
protected DatabaseAccessor getAccessor() {
return (DatabaseAccessor) getSession().getAccessor();
}
/**
* Get a description of table columns available in a catalog.
*
* <P>Each column description has the following columns:
* <OL>
* <LI><B>TABLE_CAT</B> String {@literal =>} table catalog (may be null)
* <LI><B>TABLE_SCHEM</B> String {@literal =>} table schema (may be null)
* <LI><B>TABLE_NAME</B> String {@literal =>} table name
* <LI><B>COLUMN_NAME</B> String {@literal =>} column name
* <LI><B>DATA_TYPE</B> short {@literal =>} SQL type from java.sql.Types
* <LI><B>TYPE_NAME</B> String {@literal =>} Data source dependent type name
* <LI><B>COLUMN_SIZE</B> int {@literal =>} column size. For char or date
* types this is the maximum number of characters, for numeric or
* decimal types this is precision.
* <LI><B>BUFFER_LENGTH</B> is not used.
* <LI><B>DECIMAL_DIGITS</B> int {@literal =>} the number of fractional digits
* <LI><B>NUM_PREC_RADIX</B> int {@literal =>} Radix (typically either 10 or 2)
* <LI><B>NULLABLE</B> int {@literal =>} is NULL allowed?
* <UL>
* <LI> columnNoNulls - might not allow NULL values
* <LI> columnNullable - definitely allows NULL values
* <LI> columnNullableUnknown - nullability unknown
* </UL>
* <LI><B>REMARKS</B> String {@literal =>} comment describing column (may be null)
* <LI><B>COLUMN_DEF</B> String {@literal =>} default value (may be null)
* <LI><B>SQL_DATA_TYPE</B> int {@literal =>} unused
* <LI><B>SQL_DATETIME_SUB</B> int {@literal =>} unused
* <LI><B>CHAR_OCTET_LENGTH</B> int {@literal =>} for char types the
* maximum number of bytes in the column
* <LI><B>ORDINAL_POSITION</B> int {@literal =>} index of column in table
* (starting at 1)
* <LI><B>IS_NULLABLE</B> String {@literal =>} "NO" means column definitely
* does not allow NULL values; "YES" means the column might
* allow NULL values. An empty string means nobody knows.
* </OL>
*
* @param tableName a table name pattern
* @return a Vector of Records.
*/
public Vector getAllColumnNames(String tableName) throws DatabaseException {
return getAccessor().getColumnInfo(null, null, tableName, null, getSession());
}
/**
* Get a description of table columns available in a catalog.
*
* <P>Each column description has the following columns:
* <OL>
* <LI><B>TABLE_CAT</B> String {@literal =>} table catalog (may be null)
* <LI><B>TABLE_SCHEM</B> String {@literal =>} table schema (may be null)
* <LI><B>TABLE_NAME</B> String {@literal =>} table name
* <LI><B>COLUMN_NAME</B> String {@literal =>} column name
* <LI><B>DATA_TYPE</B> short {@literal =>} SQL type from java.sql.Types
* <LI><B>TYPE_NAME</B> String {@literal =>} Data source dependent type name
* <LI><B>COLUMN_SIZE</B> int {@literal =>} column size. For char or date
* types this is the maximum number of characters, for numeric or
* decimal types this is precision.
* <LI><B>BUFFER_LENGTH</B> is not used.
* <LI><B>DECIMAL_DIGITS</B> int {@literal =>} the number of fractional digits
* <LI><B>NUM_PREC_RADIX</B> int {@literal =>} Radix (typically either 10 or 2)
* <LI><B>NULLABLE</B> int {@literal =>} is NULL allowed?
* <UL>
* <LI> columnNoNulls - might not allow NULL values
* <LI> columnNullable - definitely allows NULL values
* <LI> columnNullableUnknown - nullability unknown
* </UL>
* <LI><B>REMARKS</B> String {@literal =>} comment describing column (may be null)
* <LI><B>COLUMN_DEF</B> String {@literal =>} default value (may be null)
* <LI><B>SQL_DATA_TYPE</B> int {@literal =>} unused
* <LI><B>SQL_DATETIME_SUB</B> int {@literal =>} unused
* <LI><B>CHAR_OCTET_LENGTH</B> int {@literal =>} for char types the
* maximum number of bytes in the column
* <LI><B>ORDINAL_POSITION</B> int {@literal =>} index of column in table
* (starting at 1)
* <LI><B>IS_NULLABLE</B> String {@literal =>} "NO" means column definitely
* does not allow NULL values; "YES" means the column might
* allow NULL values. An empty string means nobody knows.
* </OL>
*
* @param creatorName a schema name pattern; "" retrieves those
* without a schema
* @param tableName a table name pattern
* @return a Vector of Records.
*/
public Vector getAllColumnNames(String creatorName, String tableName) throws DatabaseException {
return getAccessor().getColumnInfo(null, creatorName, tableName, null, getSession());
}
/**
* Get a description of tables available in a catalog.
*
* <P>Each table description has the following columns:
* <OL>
* <LI><B>TABLE_CAT</B> String {@literal =>} table catalog (may be null)
* <LI><B>TABLE_SCHEM</B> String {@literal =>} table schema (may be null)
* <LI><B>TABLE_NAME</B> String {@literal =>} table name
* <LI><B>TABLE_TYPE</B> String {@literal =>} table type. Typical types are "TABLE",
* "VIEW", "SYSTEM TABLE", "GLOBAL TEMPORARY",
* "LOCAL TEMPORARY", "ALIAS", "SYNONYM".
* <LI><B>REMARKS</B> String {@literal =>} explanatory comment on the table
* </OL>
*
* <P><B>Note:</B> Some databases may not return information for
* all tables.
*
* @return a Vector of Records.
*/
public Vector getAllTableNames() throws DatabaseException {
return getAccessor().getTableInfo(null, null, null, null, getSession());
}
/**
* Get a description of table columns available in a catalog.
*
* <P>Each column description has the following columns:
* <OL>
* <LI><B>TABLE_CAT</B> String {@literal =>} table catalog (may be null)
* <LI><B>TABLE_SCHEM</B> String {@literal =>} table schema (may be null)
* <LI><B>TABLE_NAME</B> String {@literal =>} table name
* <LI><B>COLUMN_NAME</B> String {@literal =>} column name
* <LI><B>DATA_TYPE</B> short {@literal =>} SQL type from java.sql.Types
* <LI><B>TYPE_NAME</B> String {@literal =>} Data source dependent type name
* <LI><B>COLUMN_SIZE</B> int {@literal =>} column size. For char or date
* types this is the maximum number of characters, for numeric or
* decimal types this is precision.
* <LI><B>BUFFER_LENGTH</B> is not used.
* <LI><B>DECIMAL_DIGITS</B> int {@literal =>} the number of fractional digits
* <LI><B>NUM_PREC_RADIX</B> int {@literal =>} Radix (typically either 10 or 2)
* <LI><B>NULLABLE</B> int {@literal =>} is NULL allowed?
* <UL>
* <LI> columnNoNulls - might not allow NULL values
* <LI> columnNullable - definitely allows NULL values
* <LI> columnNullableUnknown - nullability unknown
* </UL>
* <LI><B>REMARKS</B> String {@literal =>} comment describing column (may be null)
* <LI><B>COLUMN_DEF</B> String {@literal =>} default value (may be null)
* <LI><B>SQL_DATA_TYPE</B> int {@literal =>} unused
* <LI><B>SQL_DATETIME_SUB</B> int {@literal =>} unused
* <LI><B>CHAR_OCTET_LENGTH</B> int {@literal =>} for char types the
* maximum number of bytes in the column
* <LI><B>ORDINAL_POSITION</B> int {@literal =>} index of column in table
* (starting at 1)
* <LI><B>IS_NULLABLE</B> String {@literal =>} "NO" means column definitely
* does not allow NULL values; "YES" means the column might
* allow NULL values. An empty string means nobody knows.
* </OL>
*
* @param creatorName a schema name pattern; "" retrieves those
* without a schema
* @return a Vector of Records.
*/
public Vector getAllTableNames(String creatorName) throws DatabaseException {
return getAccessor().getTableInfo(null, creatorName, null, null, getSession());
}
/**
* Get a description of table columns available in a catalog.
*
* <P>Only column descriptions matching the catalog, schema, table
* and column name criteria are returned. They are ordered by
* TABLE_SCHEM, TABLE_NAME and ORDINAL_POSITION.
*
* <P>Each column description has the following columns:
* <OL>
* <LI><B>TABLE_CAT</B> String {@literal =>} table catalog (may be null)
* <LI><B>TABLE_SCHEM</B> String {@literal =>} table schema (may be null)
* <LI><B>TABLE_NAME</B> String {@literal =>} table name
* <LI><B>COLUMN_NAME</B> String {@literal =>} column name
* <LI><B>DATA_TYPE</B> short {@literal =>} SQL type from java.sql.Types
* <LI><B>TYPE_NAME</B> String {@literal =>} Data source dependent type name
* <LI><B>COLUMN_SIZE</B> int {@literal =>} column size. For char or date
* types this is the maximum number of characters, for numeric or
* decimal types this is precision.
* <LI><B>BUFFER_LENGTH</B> is not used.
* <LI><B>DECIMAL_DIGITS</B> int {@literal =>} the number of fractional digits
* <LI><B>NUM_PREC_RADIX</B> int {@literal =>} Radix (typically either 10 or 2)
* <LI><B>NULLABLE</B> int {@literal =>} is NULL allowed?
* <UL>
* <LI> columnNoNulls - might not allow NULL values
* <LI> columnNullable - definitely allows NULL values
* <LI> columnNullableUnknown - nullability unknown
* </UL>
* <LI><B>REMARKS</B> String {@literal =>} comment describing column (may be null)
* <LI><B>COLUMN_DEF</B> String {@literal =>} default value (may be null)
* <LI><B>SQL_DATA_TYPE</B> int {@literal =>} unused
* <LI><B>SQL_DATETIME_SUB</B> int {@literal =>} unused
* <LI><B>CHAR_OCTET_LENGTH</B> int {@literal =>} for char types the
* maximum number of bytes in the column
* <LI><B>ORDINAL_POSITION</B> int {@literal =>} index of column in table
* (starting at 1)
* <LI><B>IS_NULLABLE</B> String {@literal =>} "NO" means column definitely
* does not allow NULL values; "YES" means the column might
* allow NULL values. An empty string means nobody knows.
* </OL>
*
* @param catalog a catalog name; "" retrieves those without a
* catalog; null means drop catalog name from the selection criteria
* @param schema a schema name pattern; "" retrieves those
* without a schema
* @param tableName a table name pattern
* @param columnName a column name pattern
* @return a Vector of Records.
*/
public Vector getColumnInfo(String catalog, String schema, String tableName, String columnName) throws DatabaseException {
return getAccessor().getColumnInfo(catalog, schema, tableName, columnName, getSession());
}
public AbstractSession getSession() {
return session;
}
/**
* Get a description of tables available in a catalog.
*
* <P>Only table descriptions matching the catalog, schema, table
* name and type criteria are returned. They are ordered by
* TABLE_TYPE, TABLE_SCHEM and TABLE_NAME.
*
* <P>Each table description has the following columns:
* <OL>
* <LI><B>TABLE_CAT</B> String {@literal =>} table catalog (may be null)
* <LI><B>TABLE_SCHEM</B> String {@literal =>} table schema (may be null)
* <LI><B>TABLE_NAME</B> String {@literal =>} table name
* <LI><B>TABLE_TYPE</B> String {@literal =>} table type. Typical types are "TABLE",
* "VIEW", "SYSTEM TABLE", "GLOBAL TEMPORARY",
* "LOCAL TEMPORARY", "ALIAS", "SYNONYM".
* <LI><B>REMARKS</B> String {@literal =>} explanatory comment on the table
* </OL>
*
* <P><B>Note:</B> Some databases may not return information for
* all tables.
*
* @param catalog a catalog name; "" retrieves those without a
* catalog; null means drop catalog name from the selection criteria
* @param schema a schema name pattern; "" retrieves those
* without a schema
* @param tableName a table name pattern
* @param types a list of table types to include; null returns all types
* @return a Vector of Records.
*/
public Vector getTableInfo(String catalog, String schema, String tableName, String[] types) throws DatabaseException {
return getAccessor().getTableInfo(catalog, schema, tableName, types, getSession());
}
/**
* PUBLIC:
* Output all DDL statements directly to the database.
*/
public void outputDDLToDatabase() {
this.createSchemaWriter = null;
this.dropSchemaWriter = null;
}
/**
* PUBLIC:
* Output all DDL statements to a file writer specified by the name in the parameter.
*/
public void outputDDLToFile(String fileName) {
this.createSchemaWriter = getWriter(fileName);
}
public void outputCreateDDLToFile(String fileName) {
this.createSchemaWriter = getWriter(fileName);
}
public void outputDropDDLToFile(String fileName) {
this.dropSchemaWriter = getWriter(fileName);
}
protected Writer getWriter(String fileName) {
try {
return new java.io.FileWriter(fileName);
} catch (java.io.IOException ioException) {
// Try a url next, otherwise throw the existing error.
try {
URL url = new URL(fileName);
return new java.io.FileWriter(url.getFile());
} catch (Exception e) {
// MalformedURLException and IOException
throw ValidationException.fileError(ioException);
}
}
}
/**
* PUBLIC:
* Output all DDL statements to a writer specified in the parameter.
*/
public void outputDDLToWriter(Writer schemaWriter) {
this.createSchemaWriter = schemaWriter;
}
public void outputCreateDDLToWriter(Writer createWriter) {
this.createSchemaWriter = createWriter;
}
public void outputDropDDLToWriter(Writer dropWriter) {
this.dropSchemaWriter = dropWriter;
}
/**
* Use the definition object to drop and recreate the schema entity on the database.
* This is used for dropping tables, views, procedures ... etc ...
* This handles and ignore any database error while dropping in case the object did not previously exist.
*/
public void replaceObject(DatabaseObjectDefinition databaseDefinition) throws EclipseLinkException {
// PERF: Allow a special "fast" flag to be set on the session causes a delete from the table instead of a replace.
boolean fast = FAST_TABLE_CREATOR;
if (fast && (databaseDefinition instanceof TableDefinition)) {
session.executeNonSelectingSQL("DELETE FROM " + databaseDefinition.getName());
} else if (fast && (databaseDefinition instanceof StoredProcedureDefinition)) {
// do nothing
} else {
// CR 3870467, do not log stack
boolean shouldLogExceptionStackTrace = getSession().getSessionLog().shouldLogExceptionStackTrace();
if (shouldLogExceptionStackTrace) {
getSession().getSessionLog().setShouldLogExceptionStackTrace(false);
}
try {
dropObject(databaseDefinition);
} catch (DatabaseException exception) {
// Ignore error
} finally {
if (shouldLogExceptionStackTrace) {
getSession().getSessionLog().setShouldLogExceptionStackTrace(true);
}
}
createObject(databaseDefinition);
}
}
/**
* Construct the default TableCreator.
* If the default TableCreator is already created, just returns it.
*/
protected TableCreator getDefaultTableCreator(boolean generateFKConstraints) {
if(defaultTableCreator == null) {
defaultTableCreator = new DefaultTableGenerator(session.getProject(),generateFKConstraints).generateDefaultTableCreator();
defaultTableCreator.setIgnoreDatabaseException(true);
}
return defaultTableCreator;
}
/**
* Create the default table schema for the project this session associated with.
*/
public void createDefaultTables(boolean generateFKConstraints) {
//Create each table w/o throwing exception and/or exit if some of them are already existed in the db.
//If a table is already existed, skip the creation.
boolean shouldLogExceptionStackTrace = getSession().getSessionLog().shouldLogExceptionStackTrace();
getSession().getSessionLog().setShouldLogExceptionStackTrace(false);
try {
TableCreator tableCreator = getDefaultTableCreator(generateFKConstraints);
tableCreator.createTables(this.session, this);
} catch (DatabaseException ex) {
// Ignore error
} finally {
getSession().getSessionLog().setShouldLogExceptionStackTrace(shouldLogExceptionStackTrace);
}
// Reset database change events to new tables.
if (this.session.getDatabaseEventListener() != null) {
this.session.getDatabaseEventListener().remove(this.session);
this.session.getDatabaseEventListener().register(this.session);
}
}
/**
* INTERNAL:
* Iterate over the schemas that need to be dropped.
*/
public void dropDatabaseSchemas() {
for (DatabaseObjectDefinition dod : dropDatabaseSchemas.values()) {
if (shouldWriteToDatabase()) {
dod.dropDatabaseSchemaOnDatabase(getSession());
} else {
dod.dropDatabaseSchema(getSession(), getDropSchemaWriter());
appendToDDLWriter(getDropSchemaWriter(), "\n");
}
}
}
/**
* Create the default table schema for the project this session associated with.
*/
public void dropDefaultTables() {
// Drop each table w/o throwing exception and/or exit if some don't exist.
boolean shouldLogExceptionStackTrace = getSession().getSessionLog().shouldLogExceptionStackTrace();
getSession().getSessionLog().setShouldLogExceptionStackTrace(false);
try {
// Drop the tables.
TableCreator tableCreator = getDefaultTableCreator(true);
tableCreator.dropTables(this.session, this);
// Drop the sequences.
dropSequences();
// Drop all the database schemas now if set to do so. This must be
// called after all the constraints, tables etc. are dropped.
dropDatabaseSchemas();
} catch (DatabaseException ex) {
// Ignore error
} finally {
getSession().getSessionLog().setShouldLogExceptionStackTrace(shouldLogExceptionStackTrace);
}
// Reset database change events to new tables.
if (this.session.getDatabaseEventListener() != null) {
this.session.getDatabaseEventListener().remove(this.session);
this.session.getDatabaseEventListener().register(this.session);
}
}
/**
* Drop and recreate the default table schema for the project this session associated with.
*/
public void replaceDefaultTables() throws EclipseLinkException {
replaceDefaultTables(true, true, true);
}
/**
* Drop and recreate the default table schema for the project this session associated with.
*/
public void replaceDefaultTables(boolean createSequenceTables, boolean generateFKConstraints) throws EclipseLinkException {
replaceDefaultTables(createSequenceTables, false, generateFKConstraints);
}
/**
* Drop and recreate the default table schema for the project this session associated with.
*/
public void replaceDefaultTables(boolean createSequenceTables, boolean createSequences, boolean generateFKConstraints) throws EclipseLinkException {
boolean shouldLogExceptionStackTrace = getSession().getSessionLog().shouldLogExceptionStackTrace();
this.session.getSessionLog().setShouldLogExceptionStackTrace(false);
try {
TableCreator tableCreator = getDefaultTableCreator(generateFKConstraints);
tableCreator.replaceTables(this.session, this, createSequenceTables, createSequences);
// Drop all the database schemas now if set to do so. This must be
// called after all the constraints, tables etc. are dropped.
dropDatabaseSchemas();
} catch (DatabaseException exception) {
// Ignore error
} finally {
this.session.getSessionLog().setShouldLogExceptionStackTrace(shouldLogExceptionStackTrace);
}
// Reset database change events to new tables.
if (this.session.getDatabaseEventListener() != null) {
this.session.getDatabaseEventListener().remove(this.session);
this.session.getDatabaseEventListener().register(this.session);
}
}
public void setSession(DatabaseSessionImpl session) {
this.session = session;
}
/**
* INTERNAL:
* Returns true if a database schema should be created during ddl generation
* for the given databaseObjectDefinition.
*/
protected boolean shouldCreateDatabaseSchema(DatabaseObjectDefinition databaseObjectDefinition, Set<String> createdDatabaseSchemas) {
return (createDatabaseSchemas && databaseObjectDefinition.shouldCreateDatabaseSchema(createdDatabaseSchemas));
}
/**
* PUBLIC:
* Return true if this SchemaManager should write to the database directly
*/
public boolean shouldWriteToDatabase() {
return ((this.createSchemaWriter == null) && (this.dropSchemaWriter == null));
}
/**
* Use the definition to alter sequence.
*/
public void alterSequence(SequenceDefinition sequenceDefinition) throws EclipseLinkException {
if (!sequenceDefinition.isAlterSupported(getSession())) {
return;
}
boolean usesBatchWriting = false;
if (getSession().getPlatform().usesBatchWriting()) {
usesBatchWriting = true;
getSession().getPlatform().setUsesBatchWriting(false);
}
try {
if (shouldWriteToDatabase()) {
sequenceDefinition.alterOnDatabase(getSession());
} else {
sequenceDefinition.alter(getSession(), createSchemaWriter);
}
} finally {
if (usesBatchWriting) {
getSession().getPlatform().setUsesBatchWriting(true);
}
}
}
/**
* Create or extend the default table schema for the project this session associated with.
*/
public void extendDefaultTables(boolean generateFKConstraints) throws EclipseLinkException {
boolean shouldLogExceptionStackTrace = getSession().getSessionLog().shouldLogExceptionStackTrace();
this.session.getSessionLog().setShouldLogExceptionStackTrace(false);
try {
TableCreator tableCreator = getDefaultTableCreator(generateFKConstraints);
tableCreator.extendTables(this.session, this);
} catch (DatabaseException exception) {
// Ignore error
} finally {
this.session.getSessionLog().setShouldLogExceptionStackTrace(shouldLogExceptionStackTrace);
}
// Reset database change events to new tables.
if (this.session.getDatabaseEventListener() != null) {
this.session.getDatabaseEventListener().remove(this.session);
this.session.getDatabaseEventListener().register(this.session);
}
}
}