diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/config/PersistenceUnitProperties.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/config/PersistenceUnitProperties.java
index a4484497e57..1ab80393f38 100644
--- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/config/PersistenceUnitProperties.java
+++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/config/PersistenceUnitProperties.java
@@ -51,6 +51,8 @@
// - 533148 : Add the eclipselink.jpa.sql-call-deferral property
// 12/06/2018 - Will Dazey
// - 542491: Add new 'eclipselink.jdbc.force-bind-parameters' property to force enable binding
+// 12/05/2023: Tomas Kraus
+// - New Jakarta Persistence 3.2 Features
package org.eclipse.persistence.config;
import jakarta.persistence.EntityManager;
@@ -2357,6 +2359,14 @@ public class PersistenceUnitProperties {
*/
public static final String SCHEMA_GENERATION_NONE_ACTION = "none";
+ /**
+ * The parameter value "verify"
+ *
For use with the "jakarta.persistence.schema-generation.database.action"
+ * and "jakarta.persistence.schema-generation.scripts.action" properties.
+ *
Specifies that database tables should be verified.
+ */
+ public static final String SCHEMA_GENERATION_VERIFY_ACTION = "verify";
+
/**
* The parameter value "metadata"
*
For use with the "jakarta.persistence.schema-generation.create-source"
@@ -3032,7 +3042,7 @@ public class PersistenceUnitProperties {
* should be automatically generated for foreign key constraints. It is normally recommended to have
* an index for a foreign key.
*
- * By default indexes are not generated, most database also do not auto generate indexes, although some do.
+ * By default, indexes are not generated, most database also do not auto generate indexes, although some do.
*
* Allowed Values:
*
@@ -3044,6 +3054,39 @@ public class PersistenceUnitProperties {
*/
public static final String DDL_GENERATION_INDEX_FOREIGN_KEYS = "eclipselink.ddl-generation.index-foreign-keys";
+ /**
+ * The "eclipselink.schema-validation.mode" property specifies database schema validation mode.
+ * By default, the "simple" validation mode is selected.
+ * Allowed Values:
+ *
+ *
"simple" (DEFAULT)
+ *
"full" (experimental feature, may not work properly)
+ *
+ *
+ * @see #SCHEMA_VALIDATION_MODE_SIMPLE
+ * @see #SCHEMA_VALIDATION_MODE_FULL
+ */
+ public static final String SCHEMA_VALIDATION_MODE = "eclipselink.schema-validation.mode";
+
+ /**
+ * The "simple" value of the "eclipselink.schema-validation.mode" property.
+ * Specifies, that simple schema validation shall be performed.
+ * Simple schema validation checks missing tables and missing or surplus columns in the existing tables.
+ *
+ * @see #SCHEMA_VALIDATION_MODE
+ */
+ public static final String SCHEMA_VALIDATION_MODE_SIMPLE = "simple";
+
+ /**
+ * The "full" value of the "eclipselink.schema-validation.mode" property.
+ * Specifies, that full schema validation shall be performed.
+ * This feature is experimental and may not work properly on all supported databases.
+ * Full schema validation also checks differences in columns definitions.
+ *
+ * @see #SCHEMA_VALIDATION_MODE
+ */
+ public static final String SCHEMA_VALIDATION_MODE_FULL = "full";
+
/**
* The parameter value "sql-script" specifies that DDL will be written to file(s).
*
For use with the "eclipselink.ddl-generation.output-mode" property.
diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/databaseaccess/DatabasePlatform.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/databaseaccess/DatabasePlatform.java
index a54b184b048..0eb61e49425 100644
--- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/databaseaccess/DatabasePlatform.java
+++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/databaseaccess/DatabasePlatform.java
@@ -34,10 +34,10 @@
// - 542491: Add new 'eclipselink.jdbc.force-bind-parameters' property to force enable binding
// 13/01/2022-4.0.0 Tomas Kraus
// - 1391: JSON support in JPA
+// 12/05/2023: Tomas Kraus
+// - New Jakarta Persistence 3.2 Features
package org.eclipse.persistence.internal.databaseaccess;
-// javase imports
-
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.exceptions.ValidationException;
@@ -3802,7 +3802,34 @@ public void initializeConnectionData(Connection connection) throws SQLException
*/
public void writeAddColumnClause(Writer writer, AbstractSession session, TableDefinition table, FieldDefinition field) throws IOException {
writer.write("ADD ");
- field.appendDBString(writer, session, table);
+ field.appendDBCreateString(writer, session, table);
+ }
+
+ /**
+ * INTERNAL:
+ * May need to override this method if the platform supports ALTER TABLE DROP COLUMN <column>
+ * and the generated sql doesn't work.
+ * Write the string that follows ALTER TABLE to create a sql statement for
+ * the platform in order to drop existing column from an existing table.
+ */
+ public void writeDropColumnClause(Writer writer, AbstractSession session, TableDefinition table, String fieldName) throws IOException {
+ writer.write("DROP COLUMN ");
+ writer.write(fieldName);
+ }
+
+ /**
+ * INTERNAL:
+ * May need to override this method if the platform supports TRUNCATE TABLE <table>
+ * and the generated sql doesn't work.
+ * Write the string that creates TRUNCATE TABLE sql statement for the platform in order
+ * to truncate an existing table.
+ */
+ public void writeTruncateTable(Writer writer, AbstractSession session, TableDefinition table) throws IOException {
+ String tableName = table.getTable() == null
+ ? table.getName()
+ : table.getTable().getName();
+ writer.write("TRUNCATE TABLE ");
+ writer.write(tableName);
}
/**
diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/databaseaccess/FieldTypeDefinition.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/databaseaccess/FieldTypeDefinition.java
index 10778aa0c49..971465ee5b8 100644
--- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/databaseaccess/FieldTypeDefinition.java
+++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/databaseaccess/FieldTypeDefinition.java
@@ -12,9 +12,15 @@
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
+// 12/05/2023: Tomas Kraus
+// - New Jakarta Persistence 3.2 Features
package org.eclipse.persistence.internal.databaseaccess;
import java.io.Serializable;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
/**
* INTERNAL:
@@ -39,8 +45,10 @@ public class FieldTypeDefinition implements Serializable {
protected int maxScale;
protected boolean shouldAllowNull; //allow for specific types/platforms to not allow null
protected String typesuffix;
+ // All type aliases, including primary name. Type names are converted to upper case to be case-insensitive.
+ private final Set aliases;
- public FieldTypeDefinition() {
+ private FieldTypeDefinition(Set aliasesSet) {
defaultSize = 10;
isSizeRequired = false;
isSizeAllowed = true;
@@ -49,19 +57,33 @@ public FieldTypeDefinition() {
maxScale = 0;
shouldAllowNull = true;
typesuffix = null;
+ aliases = aliasesSet;
}
- /**
- * Return a new field type.
- * @see #setName(String)
- */
+ /**
+ * Creates a new instance of {@link FieldTypeDefinition}
+ */
+ public FieldTypeDefinition() {
+ this(Collections.emptySet());
+ }
+
+ /**
+ * Creates a new instance of {@link FieldTypeDefinition} with database type name,
+ * see {@link #setName(String)}.
+ *
+ * @param databaseTypeName database type name
+ */
public FieldTypeDefinition(String databaseTypeName) {
this();
name = databaseTypeName;
}
/**
- * Return a new field type with a required size defaulting to the defaultSize.
+ * Creates a new instance of {@link FieldTypeDefinition} with database type name
+ * and default required size.
+ *
+ * @param databaseTypeName database type name
+ * @param defaultSize default required size
*/
public FieldTypeDefinition(String databaseTypeName, int defaultSize) {
this();
@@ -72,7 +94,12 @@ public FieldTypeDefinition(String databaseTypeName, int defaultSize) {
}
/**
- * Return a new field type with a required size defaulting to the defaultSize.
+ * Creates a new instance of {@link FieldTypeDefinition} with database type name
+ * and default required size and sub-size.
+ *
+ * @param databaseTypeName database type name
+ * @param defaultSize default required size
+ * @param defaultSubSize default required sub-size
*/
public FieldTypeDefinition(String databaseTypeName, int defaultSize, int defaultSubSize) {
this();
@@ -84,14 +111,25 @@ public FieldTypeDefinition(String databaseTypeName, int defaultSize, int default
setMaxScale(defaultSubSize);
}
- public FieldTypeDefinition(String databaseTypeName, int defaultSize, String aTypesuffix) {
+ /**
+ * Creates a new instance of {@link FieldTypeDefinition} with database type name,
+ * default required size and type suffix.
+ *
+ * @param databaseTypeName database type name
+ * @param defaultSize default required size
+ * @param typeSuffix type suffix
+ */
+ public FieldTypeDefinition(String databaseTypeName, int defaultSize, String typeSuffix) {
this(databaseTypeName, defaultSize);
- this.typesuffix = aTypesuffix;
+ this.typesuffix = typeSuffix;
this.isSizeAllowed = true;
}
/**
- * Return a new field type with a required size defaulting to the defaultSize.
+ * Creates a new instance of {@link FieldTypeDefinition} with database type name.
+ *
+ * @param databaseTypeName database type name
+ * @param allowsSize whether database type allows size definition (e.g. {@code VARCHAR(8)}, {@code DECIMAL(15)})
*/
public FieldTypeDefinition(String databaseTypeName, boolean allowsSize) {
this();
@@ -99,8 +137,27 @@ public FieldTypeDefinition(String databaseTypeName, boolean allowsSize) {
this.isSizeAllowed = allowsSize;
}
- /** Return a new field type with a required size defaulting to the defaultSize and
- * shouldAllowNull set to allowsNull.
+ /**
+ * Creates a new instance of {@link FieldTypeDefinition} with database type name
+ * and allowable size definition.
+ *
+ * @param databaseTypeName database type name
+ * @param allowsSize whether database type allows size definition (e.g. {@code VARCHAR(8)}, {@code DECIMAL(15)})
+ * @param typeNameAliases database type name aliases (used to match type name provided by the database in schema validation)
+ */
+ public FieldTypeDefinition(String databaseTypeName, boolean allowsSize, String... typeNameAliases) {
+ this(createAliasesSet(typeNameAliases));
+ this.name = databaseTypeName;
+ this.isSizeAllowed = allowsSize;
+ }
+
+ /**
+ * Creates a new instance of {@link FieldTypeDefinition} with database type name
+ * and allowable size definition and {@code NULL} values.
+ *
+ * @param databaseTypeName database type name
+ * @param allowsSize whether database type allows size definition (e.g. {@code VARCHAR(8)}, {@code DECIMAL(15)})
+ * @param allowsNull whether database type allows @code NULL} values.
*/
public FieldTypeDefinition(String databaseTypeName, boolean allowsSize, boolean allowsNull) {
this(databaseTypeName, allowsSize);
@@ -284,6 +341,19 @@ public void setName(String name) {
this.name = name;
}
+ /**
+ * Check whether provided type name matches any known type alias.
+ * Check is case-insensitive. Provided type name shall not be null.
+ *
+ * @param nameAlias type name to check, not {@code null}
+ * @return Value of {@code true} when provided name matches type name
+ * or any of its name aliases or {@code false} otherwise.
+ */
+ public boolean isTypeName(String nameAlias) {
+ Objects.requireNonNull(nameAlias, "Checked type name is null.");
+ return nameAlias.equalsIgnoreCase(name) || aliases.contains(nameAlias.toUpperCase());
+ }
+
/**
* Set this type to not allow a size specification.
*/
@@ -310,4 +380,18 @@ public void setSizeRequired() {
public String toString() {
return getClass().getSimpleName() + "(" + getName() + ")";
}
+
+ // Constructor helper to build database type name aliases set
+ // Type name aliases are converted to upper case.
+ private static Set createAliasesSet(String... typeNameAliases) {
+ if (typeNameAliases == null || typeNameAliases.length == 0) {
+ return Collections.emptySet();
+ }
+ Set aliasesSet = new HashSet<>(typeNameAliases.length);
+ for (String typeNameAlias : typeNameAliases) {
+ aliasesSet.add(typeNameAlias.toUpperCase());
+ }
+ return Set.copyOf(aliasesSet);
+ }
+
}
diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/localization/i18n/ExceptionLocalizationResource.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/localization/i18n/ExceptionLocalizationResource.java
index 28e799a007d..d2a03ce8c3d 100644
--- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/localization/i18n/ExceptionLocalizationResource.java
+++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/localization/i18n/ExceptionLocalizationResource.java
@@ -35,6 +35,8 @@
// - 454189 : Misc message cleanup.#2
// 03/09/2016-2.6 Dalia Abo Sheasha
// - 489298: Wrap EclipseLink's Bean Validation calls in doPrivileged blocks when security is enabled
+// 12/05/2023: Tomas Kraus
+// - New Jakarta Persistence 3.2 Features
package org.eclipse.persistence.internal.localization.i18n;
import java.util.ListResourceBundle;
@@ -265,7 +267,13 @@ public class ExceptionLocalizationResource extends ListResourceBundle {
{ "json_pgsql_pgobject_conversion", "Database PGobject conversion failed."},
{ "json_pgsql_unknown_type", "Unknown JSON type returned from database."},
{ "json_ora21c_jsonvalue_to_oraclevalue", "Could not convert JsonValue to OracleJsonValue."},
- { "json_ora21c_resultset_to_jsonvalue", "Could not convert JDBC ResultSet type to JsonValue."}
+ { "json_ora21c_resultset_to_jsonvalue", "Could not convert JDBC ResultSet type to JsonValue."},
+ { "schema_validation", "Schema validation"},
+ { "schema_validation_failed", "Schema validation failed"},
+ { "schema_validation_missing_table", "The {0} table vas not found in the schema"},
+ { "schema_validation_table_surplus_columns", "The {0} table has surplus columns in the schema"},
+ { "schema_validation_table_missing_columns", "The {0} table has missing columns in the schema"},
+ { "schema_validation_table_different_columns", "The {0} table has different columns in the schema"}
};
/**
* Return the lookup table.
diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/localization/i18n/LoggingLocalizationResource.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/localization/i18n/LoggingLocalizationResource.java
index b508d767a59..cfcb84f9f51 100644
--- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/localization/i18n/LoggingLocalizationResource.java
+++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/localization/i18n/LoggingLocalizationResource.java
@@ -53,6 +53,8 @@
// - 526957 : Split the logging and trace messages
// 11/14/2017 - Dalia Abo Sheasha
// - 527273 : Minor message improvements
+// 12/05/2023: Tomas Kraus
+// - New Jakarta Persistence 3.2 Features
package org.eclipse.persistence.internal.localization.i18n;
import java.util.ListResourceBundle;
@@ -477,7 +479,13 @@ public class LoggingLocalizationResource extends ListResourceBundle {
// JPA 3.2
{ "unknown_property_type", "Unknown {0} type of {1} persistence property"},
{ "error_queryTimeoutParse", "Could not parse jakarta.persistence.query.timeout property value {0}: {1}"},
+ { "schema_default_truncate_tables_failed", "Failed to truncate tables in the default table schema: {0}"},
+ { "schema_default_create_tables_failed", "Failed to create tables in the default table schema: {0}"},
+ { "schema_default_drop_tables_failed", "Failed to drop tables in the default table schema: {0}"},
+ { "schema_default_replace_tables_failed", "Failed to replace tables in the default table schema: {0}"},
+ { "schema_default_extend_tables_failed", "Failed to extend tables in the default table schema: {0}"},
+ { "schema_drop_object_failed", "Failed to drop object in the default table schema: {0}"},
{ "validate_object_space", "validate object space." },
{ "stack_of_visited_objects_that_refer_to_the_corrupt_object", "stack of visited objects that refer to the corrupt object: {0}" },
{ "corrupt_object_referenced_through_mapping", "The following corrupt object is referenced through mapping: {0}" },
diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/platform/database/HANAPlatform.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/platform/database/HANAPlatform.java
index ef62e1e5415..9fa14a84f6d 100644
--- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/platform/database/HANAPlatform.java
+++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/platform/database/HANAPlatform.java
@@ -530,7 +530,7 @@ protected void appendCalendar(Calendar calendar, Writer writer) throws IOExcepti
@Override
public void writeAddColumnClause(Writer writer, AbstractSession session, TableDefinition table, FieldDefinition field) throws IOException {
writer.write("ADD (");
- field.appendDBString(writer, session, table);
+ field.appendDBCreateString(writer, session, table);
writer.write(")");
}
diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/platform/database/MySQLPlatform.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/platform/database/MySQLPlatform.java
index 3c0f0324c1e..d2af82841b4 100644
--- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/platform/database/MySQLPlatform.java
+++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/platform/database/MySQLPlatform.java
@@ -24,6 +24,8 @@
// - 440278: Support fractional seconds in time values
// 02/19/2015 - Rick Curtis
// - 458877 : Add national character support
+// 12/05/2023: Tomas Kraus
+// - New Jakarta Persistence 3.2 Features
package org.eclipse.persistence.platform.database;
import org.eclipse.persistence.exceptions.ValidationException;
@@ -179,7 +181,7 @@ protected Hashtable, FieldTypeDefinition> buildFieldTypes() {
Hashtable, FieldTypeDefinition> fieldTypeMapping = new Hashtable<>();
fieldTypeMapping.put(Boolean.class, new FieldTypeDefinition("TINYINT(1) default 0", false));
- fieldTypeMapping.put(Integer.class, new FieldTypeDefinition("INTEGER", false));
+ fieldTypeMapping.put(Integer.class, new FieldTypeDefinition("INTEGER", false, "INT"));
fieldTypeMapping.put(Long.class, new FieldTypeDefinition("BIGINT", false));
fieldTypeMapping.put(Float.class, new FieldTypeDefinition("FLOAT", false));
fieldTypeMapping.put(Double.class, new FieldTypeDefinition("DOUBLE", false));
diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/sessions/coordination/MetadataRefreshListener.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/sessions/coordination/MetadataRefreshListener.java
index 9be0b73720c..f9c65f8daec 100644
--- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/sessions/coordination/MetadataRefreshListener.java
+++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/sessions/coordination/MetadataRefreshListener.java
@@ -13,10 +13,12 @@
// Contributors:
// 01/19/2012-2.4 Chris Delahunt
// - 368490: Add support for Metadata to be refreshed through RCM
+// 12/05/2023: Tomas Kraus
+// - New Jakarta Persistence 3.2 Features
package org.eclipse.persistence.sessions.coordination;
import java.util.Map;
public interface MetadataRefreshListener {
- void triggerMetadataRefresh(Map properties);
+ void triggerMetadataRefresh(Map properties);
}
diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/tools/schemaframework/FieldDefinition.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/tools/schemaframework/FieldDefinition.java
index c7992fc5c19..daee241c9d1 100644
--- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/tools/schemaframework/FieldDefinition.java
+++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/tools/schemaframework/FieldDefinition.java
@@ -12,6 +12,8 @@
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
+// 12/05/2023: Tomas Kraus
+// - New Jakarta Persistence 3.2 Features
package org.eclipse.persistence.tools.schemaframework;
import org.eclipse.persistence.exceptions.ValidationException;
@@ -110,20 +112,55 @@ public FieldDefinition(String name, String typeName) {
this.name = name;
this.typeName = typeName;
}
-
/**
* INTERNAL:
* Append the database field definition string to the table creation statement.
+ *
* @param writer Target writer where to write field definition string.
* @param session Current session context.
* @param table Database table being processed.
* @throws ValidationException When invalid or inconsistent data were found.
*/
- public void appendDBString(final Writer writer, final AbstractSession session,
- final TableDefinition table) throws ValidationException {
+ public void appendDBCreateString(final Writer writer, final AbstractSession session,
+ final TableDefinition table) {
+ appendDBString(writer, session, table, null);
+ }
+
+ /**
+ * INTERNAL:
+ * Check whether field definition should allow {@code NULL} values.
+ *
+ * @param fieldType database platform specific field definition matching this field,
+ * e.g. {@code DatabaseObjectDefinition.getFieldTypeDefinition(session.getPlatform(), type, typeName)}
+ * @return Value of {@code true} when field definition should allow {@code NULL} values
+ * or {@code false} otherwise
+ */
+ public boolean shouldPrintFieldNullClause(FieldTypeDefinition fieldType) {
+ return shouldAllowNull && fieldType.shouldAllowNull();
+ }
+
+ /**
+ * INTERNAL:
+ * Append the database field definition string to the table creation/modification statement.
+ *
+ * @param writer Target writer where to write field definition string.
+ * @param session Current session context.
+ * @param table Database table being processed.
+ * @param alterKeyword Field definition is part of ALTER/MODIFY COLUMN statement
+ * and {@code alterKeyword} is appended after column name when not {@code null}
+ * @throws ValidationException When invalid or inconsistent data were found.
+ */
+ private void appendDBString(final Writer writer, final AbstractSession session,
+ final TableDefinition table, String alterKeyword) throws ValidationException {
try {
- writer.write(name);
- writer.write(" ");
+ writer.write(name);
+ writer.write(" ");
+
+ // e.g. "ALTER TABLE assets ALTER COLUMN location TYPE VARCHAR" to add "TYPE" keyword
+ if (alterKeyword != null) {
+ writer.write(alterKeyword);
+ writer.write(" ");
+ }
if (getTypeDefinition() != null) { //apply user-defined complete type definition
writer.write(typeDefinition);
@@ -141,7 +178,7 @@ public void appendDBString(final Writer writer, final AbstractSession session,
if (shouldPrintFieldIdentityClause) {
platform.printFieldIdentityClause(writer);
}
- if (shouldAllowNull && fieldType.shouldAllowNull()) {
+ if (shouldPrintFieldNullClause(fieldType)) {
platform.printFieldNullClause(writer);
} else {
platform.printFieldNotNullClause(writer);
diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/tools/schemaframework/SchemaManager.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/tools/schemaframework/SchemaManager.java
index a3d1da864c0..894e8a0b0a6 100644
--- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/tools/schemaframework/SchemaManager.java
+++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/tools/schemaframework/SchemaManager.java
@@ -19,6 +19,8 @@
// - 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.
+// 12/05/2023: Tomas Kraus
+// - New Jakarta Persistence 3.2 Features
package org.eclipse.persistence.tools.schemaframework;
import org.eclipse.persistence.descriptors.ClassDescriptor;
@@ -31,6 +33,7 @@
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.sessions.DatabaseSessionImpl;
+import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.sequencing.DefaultSequence;
import org.eclipse.persistence.sequencing.NativeSequence;
import org.eclipse.persistence.sequencing.Sequence;
@@ -39,8 +42,10 @@
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
import java.util.Vector;
+import java.util.function.Consumer;
/**
*
@@ -992,7 +997,7 @@ public void replaceObject(DatabaseObjectDefinition databaseDefinition) throws Ec
try {
dropObject(databaseDefinition);
} catch (DatabaseException exception) {
- // Ignore error
+ session.log(SessionLog.FINEST, SessionLog.DDL, "schema_drop_object_failed", exception.getLocalizedMessage());
} finally {
if (shouldLogExceptionStackTrace) {
getSession().getSessionLog().setShouldLogExceptionStackTrace(true);
@@ -1028,8 +1033,8 @@ public void createDefaultTables(boolean generateFKConstraints) {
try {
TableCreator tableCreator = getDefaultTableCreator(generateFKConstraints);
tableCreator.createTables(this.session, this);
- } catch (DatabaseException ex) {
- // Ignore error
+ } catch (DatabaseException exception) {
+ session.log(SessionLog.WARNING, SessionLog.DDL, "schema_default_create_tables_failed", exception.getLocalizedMessage());
} finally {
getSession().getSessionLog().setShouldLogExceptionStackTrace(shouldLogExceptionStackTrace);
}
@@ -1074,8 +1079,8 @@ public void dropDefaultTables() {
// 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
+ } catch (DatabaseException exception) {
+ session.log(SessionLog.WARNING, SessionLog.DDL, "schema_default_drop_tables_failed", exception.getLocalizedMessage());
} finally {
getSession().getSessionLog().setShouldLogExceptionStackTrace(shouldLogExceptionStackTrace);
}
@@ -1115,7 +1120,7 @@ public void replaceDefaultTables(boolean createSequenceTables, boolean createSeq
// called after all the constraints, tables etc. are dropped.
dropDatabaseSchemas();
} catch (DatabaseException exception) {
- // Ignore error
+ session.log(SessionLog.WARNING, SessionLog.DDL, "schema_default_replace_tables_failed", exception.getLocalizedMessage());
} finally {
this.session.getSessionLog().setShouldLogExceptionStackTrace(shouldLogExceptionStackTrace);
}
@@ -1126,6 +1131,39 @@ public void replaceDefaultTables(boolean createSequenceTables, boolean createSeq
}
}
+ /**
+ * Truncate all tables in the default table schema for the project this session is associated with.
+ *
+ * @param generateFKConstraints attempt to create fk constraints when {@code true}
+ */
+ public void truncateDefaultTables(boolean generateFKConstraints) {
+ boolean shouldLogExceptionStackTrace = getSession().getSessionLog().shouldLogExceptionStackTrace();
+ session.getSessionLog().setShouldLogExceptionStackTrace(false);
+
+ try {
+ TableCreator tableCreator = getDefaultTableCreator(generateFKConstraints);
+ tableCreator.truncateTables(session, this, generateFKConstraints);
+ } catch (DatabaseException exception) {
+ session.log(SessionLog.WARNING, SessionLog.DDL, "schema_default_truncate_tables_failed", exception.getLocalizedMessage());
+ } finally {
+ session.getSessionLog().setShouldLogExceptionStackTrace(shouldLogExceptionStackTrace);
+ }
+ }
+
+ /**
+ * Validate all tables in the default table schema for the project this session is associated with.
+ * @param onFailed optional {@link Consumer} to accept {@link List} of {@link TableValidationException}
+ * containing validation failures. Consumer is called only when validation failed
+ * and {@code onFailed} is not null
+ * @param generateFKConstraints attempt to create fk constraints when {@code true}
+ * @param full run full validation when {@code true} or simple when {@code false)
+ * @return value of {@code true} when validation passed or @code false} otherwise
+ */
+ public boolean validateDefaultTables(Consumer> onFailed, boolean generateFKConstraints, boolean full) {
+ TableCreator tableCreator = getDefaultTableCreator(generateFKConstraints);
+ return tableCreator.validateTables(session, this, onFailed, full);
+ }
+
public void setSession(DatabaseSessionImpl session) {
this.session = session;
}
@@ -1186,7 +1224,7 @@ public void extendDefaultTables(boolean generateFKConstraints) throws EclipseLin
TableCreator tableCreator = getDefaultTableCreator(generateFKConstraints);
tableCreator.extendTables(this.session, this);
} catch (DatabaseException exception) {
- // Ignore error
+ session.log(SessionLog.WARNING, SessionLog.DDL, "schema_default_extend_tables_failed", exception.getLocalizedMessage());
} finally {
this.session.getSessionLog().setShouldLogExceptionStackTrace(shouldLogExceptionStackTrace);
}
diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/tools/schemaframework/TableCreator.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/tools/schemaframework/TableCreator.java
index f1a308a179b..0380a75acef 100644
--- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/tools/schemaframework/TableCreator.java
+++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/tools/schemaframework/TableCreator.java
@@ -15,9 +15,13 @@
// Oracle - initial API and implementation from Oracle TopLink
// 02/04/2013-2.5 Guy Pelletier
// - 389090: JPA 2.1 DDL Generation Support
+// 12/05/2023: Tomas Kraus
+// - New Jakarta Persistence 3.2 Features
package org.eclipse.persistence.tools.schemaframework;
+import jakarta.persistence.PersistenceException;
import org.eclipse.persistence.exceptions.DatabaseException;
+import org.eclipse.persistence.internal.databaseaccess.FieldTypeDefinition;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.internal.sessions.AbstractSession;
@@ -28,13 +32,18 @@
import org.eclipse.persistence.sessions.DatabaseSession;
import org.eclipse.persistence.sessions.Session;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Consumer;
/**
* Purpose: This class is responsible for creating the tables defined in the project.
@@ -351,6 +360,37 @@ public void replaceTables(DatabaseSession session, SchemaManager schemaManager,
replaceTablesAndConstraints(schemaManager, session, createSequenceTables, createSequences);
}
+ /**
+ * Truncate all tables in the database.
+ *
+ * @param session current database session
+ * @param schemaManager database schema manager
+ * @param generateFKConstraints attempt to create fk constraints when {@code true}
+ */
+ void truncateTables(DatabaseSession session, SchemaManager schemaManager, boolean generateFKConstraints) {
+ TableCreator tableCreator = schemaManager.getDefaultTableCreator(generateFKConstraints);
+ String sequenceTableName = tableCreator.getSequenceTableName(session);
+ List tables = tableCreator.getTableDefinitions();
+ dropConstraints(session, schemaManager, false);
+ for (TableDefinition table : tables) {
+ if (!table.getName().equals(sequenceTableName)) {
+ try {
+ Writer stmtWriter = new StringWriter();
+ session.getPlatform().writeTruncateTable(stmtWriter, ((AbstractSession) session), table);
+ ((AbstractSession) session)
+ .priviledgedExecuteNonSelectingCall(
+ new org.eclipse.persistence.queries.SQLCall(stmtWriter.toString()));
+ } catch (DatabaseException ex) {
+ //Ignore database exception. eg. If there is no table to delete, it gives database exception.
+ throw ex;
+ } catch (IOException ex) {
+ throw new PersistenceException(ex);
+ }
+ }
+ }
+ createConstraints(tables, session, schemaManager, false);
+ }
+
protected void replaceTablesAndConstraints(SchemaManager schemaManager, DatabaseSession session, boolean createSequenceTables, boolean createSequences) {
buildConstraints(schemaManager, true);
boolean ignore = shouldIgnoreDatabaseException();
@@ -442,8 +482,77 @@ protected void extendTablesAndConstraints(SchemaManager schemaManager, DatabaseS
}
}
+ /**
+ * Validate tables in the database.
+ * Found issues are passed as {@link List} of {@link TableValidationException} to provided consumer
+ * when validation failed.
+ *
+ * @param session Active database session.
+ * @param schemaManager Database schema manipulation manager.
+ * @param onFailed optional {@link Consumer} to accept {@link List} of {@link TableValidationException}
+ * containing validation failures. Consumer is called only when validation failed
+ * and {@code onFailed} is not null.
+ * @param full run full validation when {@code true} or simple when {@code false}
+ * @return Value of {@code true} when validation passed or {@code false} otherwise.
+ */
+ public boolean validateTables(final DatabaseSession session,
+ final SchemaManager schemaManager,
+ Consumer> onFailed,
+ boolean full) {
+ List tableDefinitions = getTableDefinitions();
+ List exceptions = new ArrayList<>(tableDefinitions.size());
+ tableDefinitions.forEach(tableDefinition -> {
+ String tableName = tableDefinition.getTable() == null
+ ? tableDefinition.getName()
+ : tableDefinition.getTable().getName();
+ if (schemaManager.checkTableExists(tableDefinition)) {
+ List columnsInfo = readColumnInfo((AbstractSession) session, tableDefinition);
+ if (columnsInfo != null && !columnsInfo.isEmpty()) {
+ final Map columns = parseColumnInfo((AbstractSession) session,
+ tableDefinition,
+ columnsInfo);
+ // Database table columns check
+ CheckDatabaseColumns check = new CheckDatabaseColumns(session, columns.size(), full);
+ processColumnns(tableDefinition,
+ columns,
+ check::checkExisting,
+ check::addMissing,
+ check::surplusColumns);
+ // Missing columns
+ if (!check.getMissingColumns().isEmpty()) {
+ exceptions.add(
+ new TableValidationException.MissingColumns(tableName, List.copyOf(check.getMissingColumns())));
+ }
+ // Surplus columns
+ if (!check.getSurplusFields().isEmpty()) {
+ exceptions.add(
+ new TableValidationException.SurplusColumns(
+ tableName,
+ List.copyOf(check.getSurplusFields().stream().map(DatabaseField::getName).toList())));
+ }
+ if (!check.getExistingColumnsDiff().isEmpty()) {
+ exceptions.add(
+ new TableValidationException.DifferentColumns(tableName, List.copyOf(check.getExistingColumnsDiff())));
+ }
+ }
+ } else {
+ exceptions.add(new TableValidationException.MissingTable(tableName));
+ }
+ });
+ if (exceptions.isEmpty()) {
+ return true;
+ } else {
+ // Pass validation failures to provided consumer
+ if (onFailed != null) {
+ onFailed.accept(exceptions);
+ }
+ return false;
+ }
+ }
+
/**
* 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.
@@ -475,72 +584,25 @@ public void extendTables(final DatabaseSession session, final SchemaManager sche
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
+ //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 columnInfo = null;
-
- columnInfo = abstractSession.getAccessor().getColumnInfo(tableName, null, abstractSession);
-
- if (!usesDelimiting && (columnInfo == null || columnInfo.isEmpty()) ) {
- tableName = tableName.toUpperCase();
- columnInfo = abstractSession.getAccessor().getColumnInfo(tableName, null, abstractSession);
- if (( columnInfo == null || columnInfo.isEmpty()) ){
- tableName = tableName.toLowerCase();
- columnInfo = abstractSession.getAccessor().getColumnInfo(tableName, null, abstractSession);
- }
- }
+
+ List columnInfo = readColumnInfo(abstractSession, table);
+
if (columnInfo != null && !columnInfo.isEmpty()) {
- //Table exists, add individual fields as necessary
-
- //hash the table's existing columns by name
- final Map 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();
- }
+ // Table exists, parse read columns
+ final Map columns = parseColumnInfo(abstractSession, table, columnInfo);
+ // Add missing fields to the database
+ processMissingColumnns(table, columns, (fieldDef, dbField) -> {
+ 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;
}
}
- }
- 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()){
@@ -575,4 +637,277 @@ public void extendTables(final DatabaseSession session, final SchemaManager sche
session.getDatasourcePlatform().initIdentitySequences(session, DEFAULT_IDENTITY_GENERATOR);
}
+
+ // Reads column information from the database.
+ private List readColumnInfo(AbstractSession session, TableDefinition table) {
+ String tableName = table.getTable() == null ? table.getName() : table.getTable().getName();
+ boolean notUsesDelimiting = table.getTable() == null || !table.getTable().shouldUseDelimiters();
+
+ List columnInfo = session.getAccessor().getColumnInfo(tableName, null, session);
+ if (notUsesDelimiting && (columnInfo == null || columnInfo.isEmpty()) ) {
+ tableName = tableName.toUpperCase();
+ columnInfo = session.getAccessor().getColumnInfo(tableName, null, session);
+ if (( columnInfo == null || columnInfo.isEmpty()) ){
+ tableName = tableName.toLowerCase();
+ columnInfo = session.getAccessor().getColumnInfo(tableName, null, session);
+ }
+ }
+ return columnInfo;
+ }
+
+ // Parse column information read from the database.
+ private static Map parseColumnInfo(AbstractSession session,
+ TableDefinition table,
+ List columnInfo) {
+ // Hash the table's existing columns by name
+ final Map columns = new HashMap<>(columnInfo.size());
+ final DatabaseField columnNameLookupField = new DatabaseField("COLUMN_NAME");
+ final DatabaseField schemaLookupField = new DatabaseField("TABLE_SCHEM");
+ boolean schemaMatchFound = false;
+ // Determine the probable 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 username, 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);
+ }
+ }
+ }
+ return columns;
+ }
+
+ // Run provided action for each column missing in the database.
+ private static void processMissingColumnns(TableDefinition table,
+ Map columns,
+ CheckDatabaseColumns.MissingField missingAction) {
+ processColumnns(table, columns, null, missingAction, null);
+ }
+
+ // Run provided action for each column missing in the database.
+ // Optionally provide set of database columns not present in the TableDefinition.
+ private static void processColumnns(TableDefinition table,
+ Map columns,
+ CheckDatabaseColumns.ExistingField existingAction,
+ CheckDatabaseColumns.MissingField missingAction,
+ CheckDatabaseColumns.SurplusFields surplusAction) {
+ // Surplus database fields if consumer was provided
+ boolean isSurplusAction = surplusAction != null;
+ Set surplusSet = isSurplusAction ? new HashSet<>(columns.keySet()) : null;
+ // Process all fields from TableDefinition
+ for (final FieldDefinition fieldDef : table.getFields()) {
+ DatabaseField dbField = fieldDef.getDatabaseField();
+ if (dbField == null) {
+ dbField = new DatabaseField(fieldDef.getName());
+ }
+ // Run action for missing column
+ AbstractRecord dbColumn = columns.get(dbField);
+ if (dbColumn == null && missingAction != null) {
+ missingAction.accept(fieldDef, dbField);
+ // Handle existing column
+ } else {
+ // Run action for existing column
+ if (existingAction != null) {
+ existingAction.accept(fieldDef, dbField, dbColumn);
+ }
+ // Update surplus columns set
+ if (isSurplusAction) {
+ surplusSet.remove(dbField);
+ }
+ }
+ }
+ // Supply set of database columns not present in the TableDefinition when requested
+ if (isSurplusAction) {
+ surplusAction.accept(surplusSet);
+ }
+ }
+
+ // Tables validator
+ private static final class CheckDatabaseColumns {
+
+ // Current database session
+ final DatabaseSession session;
+ // Run full validation when {@code true} or simple when {@code false)
+ final boolean full;
+ // List of missing columns
+ final List missingColumns;
+ // List of differences found in existing columns
+ final List existingColumnsDiff;
+ // List of surplus columns, this set is built in processColumnns method
+ Set surplusFields;
+
+ private CheckDatabaseColumns(DatabaseSession session, int size, boolean full) {
+ this.session = session;
+ this.full = full;
+ this.missingColumns = new ArrayList<>(size);
+ this.existingColumnsDiff = new LinkedList<>();
+ this.surplusFields = null;
+ }
+
+ // Database columns check callback for missing column (existing in TableDefinition but not in database)
+ private void addMissing(FieldDefinition fieldDefinition, DatabaseField databaseField) {
+ missingColumns.add(databaseField.getName());
+ }
+
+ // Database columns check callback for column existing in both TableDefinition and the database
+ private void checkExisting(FieldDefinition fieldDefinition, DatabaseField databaseField, AbstractRecord dbRecord) {
+ // Existing columns validation only in full mode
+ if (full) {
+ FieldTypeDefinition expectedDbType = DatabaseObjectDefinition.getFieldTypeDefinition(session.getPlatform(),
+ fieldDefinition.getType(),
+ fieldDefinition.getTypeName());
+ String dbTypeName = (String) dbRecord.get("TYPE_NAME");
+ if (dbTypeName != null) {
+ // Type mismatch. DB typeName may be an alias, e.g. INT/INTEGER!
+ if (!expectedDbType.isTypeName(dbTypeName)) {
+ existingColumnsDiff.add(
+ new TableValidationException.DifferentColumns.TypeDifference(databaseField.getName(),
+ expectedDbType.getName(),
+ dbTypeName));
+ }
+ // Nullable mismatch
+ Nullable dbNullable = dbColumnNullable(dbRecord);
+ // Check only for known definition
+ if (dbNullable != Nullable.UNKNOWN) {
+ // Based on identical check in FieldDefinition#appendDBString(Writer, AbstractSession, TableDefinition, String)
+ boolean modelIsNullable = fieldDefinition.shouldPrintFieldNullClause(expectedDbType);
+ switch (dbNullable) {
+ case NO:
+ if (modelIsNullable) {
+ existingColumnsDiff.add(
+ new TableValidationException.DifferentColumns.NullableDifference(databaseField.getName(),
+ modelIsNullable,
+ false));
+ }
+ break;
+ case YES:
+ if (!modelIsNullable) {
+ existingColumnsDiff.add(
+ new TableValidationException.DifferentColumns.NullableDifference(databaseField.getName(),
+ modelIsNullable,
+ true));
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Database columns check callback for set of surplus fields (existing in database but not in TableDefinition)
+ private void surplusColumns(Set databaseFields) {
+ this.surplusFields = databaseFields;
+ }
+
+ private Set getSurplusFields() {
+ return surplusFields;
+ }
+
+ private List getMissingColumns() {
+ return missingColumns;
+ }
+
+ private List getExistingColumnsDiff() {
+ return existingColumnsDiff;
+ }
+
+ // Existing fields processing callback definition
+ @FunctionalInterface
+ private interface ExistingField {
+ void accept(FieldDefinition fieldDefinition, DatabaseField databaseField, AbstractRecord dbRecord);
+ }
+
+ // Missing fields processing callback definition
+ @FunctionalInterface
+ private interface MissingField {
+ void accept(FieldDefinition fieldDefinition, DatabaseField databaseField);
+ }
+
+ // Surplus fields set callback definition
+ @FunctionalInterface
+ private interface SurplusFields {
+ void accept(Set surplusFields);
+ }
+
+ // Database column description may contain NULLABLE and/or IS_NULLABLE.
+ // According to description in org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor and DB manuals:
+ // IS_NULLABLE: The column nullability.
+ // The value is YES if NULL values can be stored in the column, NO if not.
+ // NULLABLE: The column nullability as INTEGER value as boolean.
+ // The value is 1 if NULL values can be stored in the column, 0 if not.
+ // Use one of those values if present or return UNKNOWN when nothing was found.
+ private Nullable dbColumnNullable(AbstractRecord dbRecord) {
+ Nullable result = Nullable.parseIsNullable((String) dbRecord.get("IS_NULLABLE"));
+ if (result == Nullable.UNKNOWN) {
+ result = Nullable.parseNullable((Integer) dbRecord.get("NULLABLE"));
+ }
+ return result;
+ }
+
+ // Nullability definition of the database column
+ private enum Nullable {
+ UNKNOWN,
+ YES,
+ NO;
+
+ // Parse IS_NULLABLE database column description
+ private static Nullable parseIsNullable(String isNullable) {
+ if (isNullable == null) {
+ return UNKNOWN;
+ }
+ switch (isNullable.toUpperCase()) {
+ case "NO": return NO;
+ case "YES": return YES;
+ default: return UNKNOWN;
+ }
+ }
+
+ // Parse NULLABLE database column description
+ private static Nullable parseNullable(Integer nullable) {
+ if (nullable == null) {
+ return UNKNOWN;
+ }
+ switch (nullable) {
+ case 0: return NO;
+ case 1: return YES;
+ default: return UNKNOWN;
+ }
+ }
+
+ }
+
+ }
+
}
diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/tools/schemaframework/TableDefinition.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/tools/schemaframework/TableDefinition.java
index bbac93712d7..79d796f5425 100644
--- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/tools/schemaframework/TableDefinition.java
+++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/tools/schemaframework/TableDefinition.java
@@ -22,6 +22,8 @@
// - 389090: JPA 2.1 DDL Generation Support (foreign key metadata support)
// 02/04/2013-2.5 Guy Pelletier
// - 389090: JPA 2.1 DDL Generation Support
+// 12/05/2023: Tomas Kraus
+// - New Jakarta Persistence 3.2 Features
package org.eclipse.persistence.tools.schemaframework;
import org.eclipse.persistence.exceptions.DatabaseException;
@@ -145,6 +147,37 @@ public Writer buildAddFieldWriter(AbstractSession session, FieldDefinition field
return writer;
}
+ /**
+ * INTERNAL:
+ * Execute the SQL alter table to drop the field from the table.
+ *
+ * @param session current database session
+ * @param fieldName name of the field to drop
+ */
+ public void dropFieldOnDatabase(final AbstractSession session, String fieldName) {
+ session.priviledgedExecuteNonSelectingCall(
+ new SQLCall(buildDropFieldWriter(session, fieldName, new StringWriter()).toString()));
+ }
+
+ /**
+ * INTERNAL:
+ * Return the alter table statement to drop the field from the table.
+ *
+ * @param session current database session
+ * @param fieldName name of the field to drop
+ * @param writer target character stream writer
+ */
+ public Writer buildDropFieldWriter(AbstractSession session, String fieldName, Writer writer) throws ValidationException {
+ try {
+ writer.write("ALTER TABLE " + getFullName() + " ");
+ session.getPlatform().writeDropColumnClause(writer, session, this, fieldName);
+ writer.write(" ");
+ } catch (IOException ioException) {
+ throw ValidationException.fileError(ioException);
+ }
+ return writer;
+ }
+
/**
* PUBLIC:
* Add a foreign key constraint to the table.
@@ -411,7 +444,7 @@ public Writer buildCreationWriter(AbstractSession session, Writer writer) throws
writer.write(getCreationPrefix() + getFullName() + " (");
for (Iterator itetrator = getFields().iterator(); itetrator.hasNext();) {
FieldDefinition field = itetrator.next();
- field.appendDBString(writer, session, this);
+ field.appendDBCreateString(writer, session, this);
if (itetrator.hasNext()) {
writer.write(", ");
}
diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/tools/schemaframework/TableValidationException.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/tools/schemaframework/TableValidationException.java
new file mode 100644
index 00000000000..a1fc2d5f30c
--- /dev/null
+++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/tools/schemaframework/TableValidationException.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (c) 1998, 2023 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:
+// 11/29/2023: Tomas Kraus
+// - New Jakarta Persistence 3.2 Features
+package org.eclipse.persistence.tools.schemaframework;
+
+import java.util.List;
+
+import org.eclipse.persistence.internal.localization.ExceptionLocalization;
+
+/**
+ * Thrown when database table validation fails.
+ * Instances of this {@link Exception} are passed as {@link jakarta.persistence.SchemaValidationException#getFailures()}
+ * for each problem found during the schema validation process.
+ */
+public abstract class TableValidationException extends Exception {
+ private final String table;
+ private final ValidationFailure failure;
+
+ private TableValidationException(String message, String table, ValidationFailure type) {
+ super(message);
+ this.table = table;
+ this.failure = type;
+ }
+
+ /**
+ * Name of the database table which failed the validation.
+ *
+ * @return name of the database table
+ */
+ public String getTable() {
+ return table;
+ }
+
+ /**
+ * Validation failure.
+ * Specific failure type, e.g. missing table, missing column, surplus column, column definition differs.
+ * Specific {@link TableValidationException} instance is returned for each failure type.
+ *
+ * @return the validation failure
+ */
+ public ValidationFailure getFailure() {
+ return failure;
+ }
+
+ /**
+ * Unwrap {@link TableValidationException} as specific subtype.
+ * The specific {@link TableValidationException} subtype depends on the {@link ValidationFailure} type.
+ *
+ * @param type the {@link TableValidationException} specific subtype matching the {@link ValidationFailure} type.
+ * @return the {@link TableValidationException} cast to the specific subtype
+ * @param the {@link TableValidationException} subtype class
+ */
+ public abstract T unwrap(Class type);
+
+ /**
+ * Thrown when database table validation fails with the table missing in the database.
+ * This {@link TableValidationException} specific subtype is matching the {@link ValidationFailure#MISSING_TABLE} type.
+ */
+ public static final class MissingTable extends TableValidationException {
+
+ MissingTable(String table) {
+ super(ExceptionLocalization.buildMessage(
+ "schema_validation_missing_table", new String[] {table}),
+ table, ValidationFailure.MISSING_TABLE);
+ }
+
+ @Override
+ public T unwrap(Class cls) {
+ if (cls == MissingTable.class) {
+ return cls.cast(this);
+ }
+ throw new IllegalArgumentException(
+ String.format("Cannot cast this TableValidationException.MissingTable implementation as %s", cls.getName()));
+ }
+
+ }
+
+ /**
+ * Thrown when database table validation fails with one or more table's columns missing in the database.
+ * This {@link TableValidationException} specific subtype is matching the {@link ValidationFailure#MISSING_COLUMNS} type.
+ */
+ public static final class MissingColumns extends TableValidationException {
+
+ private final List columns;
+
+ MissingColumns(String table, List columns) {
+ super(ExceptionLocalization.buildMessage(
+ "schema_validation_table_missing_columns", new String[] {table}),
+ table, ValidationFailure.MISSING_COLUMNS);
+ this.columns = columns;
+ }
+
+ /**
+ * List of the missing database columns names.
+ *
+ * @return missing columns names
+ */
+ public List getColumns() {
+ return columns;
+ }
+
+ @Override
+ public T unwrap(Class cls) {
+ if (cls == MissingColumns.class) {
+ return cls.cast(this);
+ }
+ throw new IllegalArgumentException(
+ String.format("Cannot cast this TableValidationException.MissingColumns implementation as %s", cls.getName()));
+ }
+
+ }
+
+ /**
+ * Thrown when database table validation fails with one or more table's surplus columns in the database.
+ * This {@link TableValidationException} specific subtype is matching the {@link ValidationFailure#SURPLUS_COLUMNS} type.
+ */
+ public static final class SurplusColumns extends TableValidationException {
+
+ private final List columns;
+
+ SurplusColumns(String table, List columns) {
+ super(ExceptionLocalization.buildMessage(
+ "schema_validation_table_surplus_columns", new String[] {table}),
+ table, ValidationFailure.SURPLUS_COLUMNS);
+ this.columns = columns;
+ }
+
+ /**
+ * List of the surplus database columns names.
+ *
+ * @return surplus columns names
+ */
+ public List getColumns() {
+ return columns;
+ }
+
+ @Override
+ public T unwrap(Class cls) {
+ if (cls == SurplusColumns.class) {
+ return cls.cast(this);
+ }
+ throw new IllegalArgumentException(
+ String.format("Cannot cast this TableValidationException.SurplusColumns implementation as %s", cls.getName()));
+ }
+
+ }
+
+ /**
+ * Thrown when database table validation fails with one or more table's columns have different definition in the database.
+ * This {@link TableValidationException} specific subtype is matching the {@link ValidationFailure#DIFFERENT_COLUMNS} type.
+ */
+ public static final class DifferentColumns extends TableValidationException {
+
+ private final List differences;
+
+ DifferentColumns(String table, List columns) {
+ super(ExceptionLocalization.buildMessage(
+ "schema_validation_table_different_columns", new String[] {table}),
+ table, ValidationFailure.DIFFERENT_COLUMNS);
+ this.differences = columns;
+ }
+
+ /**
+ * List of columns differences description.
+ *
+ * @return columns differences description
+ */
+ public List getDifferences() {
+ return differences;
+ }
+
+ @Override
+ public T unwrap(Class cls) {
+ if (cls == DifferentColumns.class) {
+ return cls.cast(this);
+ }
+ throw new IllegalArgumentException(
+ String.format("Cannot cast this TableValidationException.DifferentColumns implementation as %s", cls.getName()));
+ }
+
+ /**
+ * Column difference description.
+ */
+ public static abstract class Difference {
+
+ private final String columnName;
+ private final Type type;
+
+ private Difference(String columnName, Type type) {
+ this.columnName = columnName;
+ this.type = type;
+ }
+
+ /**
+ * Unwrap {@link Difference} as specific subtype.
+ * The specific {@link Difference} subtype depends on the {@link Type}.
+ *
+ * @param type the {@link Difference} specific subtype matching the {@link Type}.
+ * @return the {@link Difference} cast to the specific subtype
+ * @param the {@link Difference} subtype class
+ */
+ public abstract T unwrap(Class type);
+
+ /**
+ * Name of the database column.
+ *
+ * @return name of the database column
+ */
+ public String getColumnName() {
+ return columnName;
+ }
+
+ /**
+ * Type of the difference in the column definition.
+ * E.g. E.g. type difference or nullable difference. Each specific difference has its own {@link Difference} subtype.
+ *
+ * @return type of the difference
+ */
+ public Type getType() {
+ return type;
+ }
+
+ }
+
+ /**
+ * Column type difference description.
+ */
+ public static class TypeDifference extends Difference {
+
+ private final String modelValue;
+ private final String dbValue;
+
+ TypeDifference(String columnName, String modelValue, String dbValue) {
+ super(columnName, Type.TYPE_DIFFERENCE);
+ this.dbValue = dbValue;
+ this.modelValue = modelValue;
+ }
+
+ @Override
+ public T unwrap(Class cls) {
+ if (cls == TypeDifference.class) {
+ return cls.cast(this);
+ }
+ throw new IllegalArgumentException(
+ String.format("Cannot cast this Difference.TypeDifference implementation as %s", cls.getName()));
+ }
+
+ /**
+ * Column type name from the database.
+ *
+ * @return column type name
+ */
+ public String getDbValue() {
+ return dbValue;
+ }
+
+ /**
+ * Column type name from the {@link jakarta.persistence.Entity} model.
+ *
+ * @return column type name
+ */
+ public String getModelValue() {
+ return modelValue;
+ }
+
+ }
+
+ public static class NullableDifference extends Difference {
+
+ private final boolean modelNullable;
+ private final boolean dbNullable;
+
+ NullableDifference(String columnName, boolean modelNullable, boolean dbNullable) {
+ super(columnName, Type.NULLABLE_DIFFERENCE);
+ this.dbNullable = dbNullable;
+ this.modelNullable = modelNullable;
+ }
+
+ @Override
+ public T unwrap(Class cls) {
+ if (cls == NullableDifference.class) {
+ return cls.cast(this);
+ }
+ throw new IllegalArgumentException(
+ String.format("Cannot cast this Difference.NullableDifference implementation as %s", cls.getName()));
+ }
+
+ /**
+ * Whether database column type definition allows {@code NULL} values.
+ *
+ * @return type definition allows {@code NULL} values then {@code true}
+ */
+ public boolean isDbNullable() {
+ return dbNullable;
+ }
+
+ /**
+ * Whether {@link jakarta.persistence.Entity} model column type definition allows {@code NULL} values.
+ *
+ * @return type definition allows {@code NULL} values then {@code true}
+ */
+ public boolean isModelNullable() {
+ return modelNullable;
+ }
+
+ }
+
+ /**
+ * Type of the difference.
+ * E.g. type difference or nullable difference.
+ */
+ public enum Type {
+ /** Type difference. Type definition differs in SQL type. */
+ TYPE_DIFFERENCE(TypeDifference.class),
+ /** Nullable difference. Type definition differs in allowing {@code NULL} values. */
+ NULLABLE_DIFFERENCE(NullableDifference.class);
+
+ private final Class extends Difference> differenceClass;
+
+ Type(Class extends Difference> differenceClass) {
+ this.differenceClass = differenceClass;
+ }
+
+ /**
+ * Returns {@link Difference} subtype matching this {@link Type}.
+ *
+ * @return {@link Difference} subtype
+ */
+ public Class extends Difference> differenceClass() {
+ return differenceClass;
+ }
+
+ }
+
+ }
+
+ /**
+ * Validation failure.
+ */
+ public enum ValidationFailure {
+ /** Missing table in the schema. */
+ MISSING_TABLE(MissingTable.class),
+ /** Table with missing columns in the schema. */
+ MISSING_COLUMNS(MissingColumns.class),
+ /** Table with surplus columns in the schema. */
+ SURPLUS_COLUMNS(SurplusColumns.class),
+ /** Table with different columns in the schema. */
+ DIFFERENT_COLUMNS(DifferentColumns.class);
+
+ private final Class extends TableValidationException> exceptionClass;
+
+ ValidationFailure(Class extends TableValidationException> exceptionClass) {
+ this.exceptionClass = exceptionClass;
+ }
+
+ /**
+ * Returns {@link TableValidationException} subtype matching this {@link ValidationFailure}.
+ *
+ * @return {@link TableValidationException} subtype
+ */
+ public Class extends TableValidationException> exceptionClass() {
+ return exceptionClass;
+ }
+
+ }
+
+}
diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/Persistence32TableCreator.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/Persistence32TableCreator.java
deleted file mode 100644
index a2753454dcd..00000000000
--- a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/java/org/eclipse/persistence/testing/models/jpa/persistence32/Persistence32TableCreator.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (c) 2023 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
- */
-package org.eclipse.persistence.testing.models.jpa.persistence32;
-
-import org.eclipse.persistence.testing.framework.TogglingFastTableCreator;
-import org.eclipse.persistence.tools.schemaframework.TableDefinition;
-
-public class Persistence32TableCreator extends TogglingFastTableCreator {
-
- public Persistence32TableCreator() {
- setName("Persistence32Project");
- addTableDefinition(buildTeamTable());
- addTableDefinition(buildTrainerTable());
- addTableDefinition(buildTypeTable());
- addTableDefinition(buildPokemonTable());
- addTableDefinition(buildPokemonTypeTable());
- addTableDefinition(buildSyntaxEntityTable());
- }
-
- public static TableDefinition buildTeamTable() {
- TableDefinition table = new TableDefinition();
- table.setName("PERSISTENCE32_TEAM");
- table.addField(createNumericPk("ID"));
- table.addField(createStringColumn("NAME", 64, false));
- return table;
- }
-
- public static TableDefinition buildTrainerTable() {
- TableDefinition table = new TableDefinition();
- table.setName("PERSISTENCE32_TRAINER");
- table.addField(createNumericPk("ID"));
- table.addField(createStringColumn("NAME", 64, false));
- table.addField(createNumericFk("TEAM_ID", 15,"PERSISTENCE32_TEAM.ID", false));
- return table;
- }
-
- public static TableDefinition buildTypeTable() {
- TableDefinition table = new TableDefinition();
- table.setName("PERSISTENCE32_TYPE");
- table.addField(createNumericPk("ID"));
- table.addField(createStringColumn("NAME", 64, false));
- return table;
- }
-
- public static TableDefinition buildPokemonTable() {
- TableDefinition table = new TableDefinition();
- table.setName("PERSISTENCE32_POKEMON");
- table.addField(createNumericPk("ID"));
- table.addField(createNumericFk("TRAINER_ID", 15,"PERSISTENCE32_TRAINER.ID", true));
- table.addField(createStringColumn("NAME", 64, false));
- return table;
- }
-
- public static TableDefinition buildPokemonTypeTable() {
- TableDefinition table = new TableDefinition();
- table.setName("PERSISTENCE32_POKEMON_TYPE");
- table.addField(createNumericFk("POKEMON_ID", "PERSISTENCE32_POKEMON.ID"));
- table.addField(createNumericFk("TYPE_ID", "PERSISTENCE32_TYPE.ID"));
- return table;
- }
-
- public static TableDefinition buildSyntaxEntityTable() {
- TableDefinition table = new TableDefinition();
- table.setName("PERSISTENCE32_SYNTAX_ENTITY");
- table.addField(createNumericPk("ID"));
- table.addField(createStringColumn("STR_VAL_1", 128, true));
- table.addField(createStringColumn("STR_VAL_2", 128, true));
- table.addField(createNumericColumn("INT_VAL", 15, true));
- table.addField(createTimeColumn("TIME_VAL", 3, true));
- table.addField(createDateColumn("DATE_VAL", 3, true));
- return table;
- }
-
-}
diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/resources/META-INF/persistence.xml b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/resources/META-INF/persistence.xml
index 81008711a8a..4170ffe629b 100644
--- a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/resources/META-INF/persistence.xml
+++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/main/resources/META-INF/persistence.xml
@@ -11,11 +11,12 @@
SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
-->
-
+ xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence
+ https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd"
+ version="3.0">
@@ -28,6 +29,25 @@
true
+
+
+
+
+
+
+
+
+
+
+ org.eclipse.persistence.jpa.PersistenceProvider
+ org.eclipse.persistence.testing.models.jpa.persistence32.Team
+ org.eclipse.persistence.testing.models.jpa.persistence32.Trainer
+ org.eclipse.persistence.testing.models.jpa.persistence32.Pokemon
+ org.eclipse.persistence.testing.models.jpa.persistence32.Type
+ org.eclipse.persistence.testing.models.jpa.persistence32.SyntaxEntity
+ true
+
+
diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/AbstractPokemon.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/AbstractPokemon.java
index c3a767ed9f8..34258ec09f8 100644
--- a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/AbstractPokemon.java
+++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/AbstractPokemon.java
@@ -15,19 +15,14 @@
import java.util.Map;
import jakarta.persistence.EntityManager;
-import junit.framework.TestSuite;
-import org.eclipse.persistence.internal.jpa.EntityManagerFactoryImpl;
-import org.eclipse.persistence.jpa.JpaEntityManagerFactory;
-import org.eclipse.persistence.testing.framework.jpa.junit.JUnitTestCase;
-import org.eclipse.persistence.testing.models.jpa.persistence32.Persistence32TableCreator;
import org.eclipse.persistence.testing.models.jpa.persistence32.Team;
import org.eclipse.persistence.testing.models.jpa.persistence32.Trainer;
import org.eclipse.persistence.testing.models.jpa.persistence32.Type;
/**
- * Abstract jUnit test suite with Pokemon model.
+ * {@link AbstractSuite} with Pokemon model.
*/
-public abstract class AbstractPokemon extends JUnitTestCase {
+public abstract class AbstractPokemon extends AbstractSuite {
// Trainer's teams
static final Team[] TEAMS = new Team[] {
@@ -68,30 +63,17 @@ public abstract class AbstractPokemon extends JUnitTestCase {
};
/**
- * Build test suite.
- * Adds model test setup as first and model test cleanup as last test
- * in the returned tests collection.
- *
- * @param name name of the suite
- * @param tests tests to add to the suite
- * @return collection of tests to execute
+ * Creates an instance of {@link AbstractPokemon}.
*/
- static TestSuite suite(String name, AbstractPokemon... tests) {
- TestSuite suite = new TestSuite();
- suite.setName(name);
- suite.addTest(new AbstractPokemon("testSetup") {});
- for (AbstractPokemon test : tests) {
- suite.addTest(test);
- }
- suite.addTest(new AbstractPokemon("testCleanup") {});
- return suite;
- }
-
- JpaEntityManagerFactory emf = null;
-
public AbstractPokemon() {
+ super();
}
+ /**
+ * Creates an instance of {@link AbstractPokemon} with custom test case name.
+ *
+ * @param name name of the test case
+ */
public AbstractPokemon(String name) {
super(name);
setPuName(getPersistenceUnitName());
@@ -102,33 +84,11 @@ public String getPersistenceUnitName() {
return "persistence32";
}
- @Override
- public void setUp() {
- super.setUp();
- emf = getEntityManagerFactory(getPersistenceUnitName())
- .unwrap(EntityManagerFactoryImpl.class);
- }
- /**
- * Return all pokemon types as ID {@link Map}.
- *
- * @param em {@link EntityManager} instance to execute the query
- * @return {@link Map} with pokemon types
- */
- Map pokemonTypes(EntityManager em) {
- Map types = new HashMap<>(TYPES.length);
- em.createNamedQuery("Type.all", Type.class)
- .getResultList()
- .forEach(type -> types.put(type.getId(), type));
- return types;
- }
-
- /**
- * The setup is done as a test, both to record its failure, and to allow
- * execution in the server.
- */
- public void testSetup() {
- new Persistence32TableCreator().replaceTables(JUnitTestCase.getServerSession(getPersistenceUnitName()));
+ // Initialize data
+ @Override
+ protected void suiteSetUp() {
+ super.suiteSetUp();
emf.runInTransaction(em -> {
for (int i = 1; i < TEAMS.length; i++) {
em.persist(TEAMS[i]);
@@ -144,23 +104,17 @@ public void testSetup() {
}
/**
- * The setup is done as a test, both to record its failure, and to allow
- * execution in the server.
+ * Return all pokemon types as ID {@link Map}.
+ *
+ * @param em {@link EntityManager} instance to execute the query
+ * @return {@link Map} with pokemon types
*/
- public void testCleanup() {
- emf.runInTransaction(em -> {
- em.createNamedQuery("Pokemon.deleteAllTypes").executeUpdate();
- em.createNamedQuery("Pokemon.deleteAll").executeUpdate();
- em.createNamedQuery("Type.deleteAll").executeUpdate();
- em.createNamedQuery("Trainer.deleteAll").executeUpdate();
- em.createNamedQuery("Team.deleteAll").executeUpdate();
- });
- }
-
- @Override
- public void clearCache() {
- emf.getCache().evictAll();
- super.clearCache();
+ Map pokemonTypes(EntityManager em) {
+ Map types = new HashMap<>(TYPES.length);
+ em.createNamedQuery("Type.all", Type.class)
+ .getResultList()
+ .forEach(type -> types.put(type.getId(), type));
+ return types;
}
}
diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/AbstractSchemaManager.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/AbstractSchemaManager.java
new file mode 100644
index 00000000000..3fb633b06c1
--- /dev/null
+++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/AbstractSchemaManager.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 1998, 2023 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
+ */
+package org.eclipse.persistence.testing.tests.jpa.persistence32;
+
+import java.util.Set;
+
+import junit.framework.TestSuite;
+import org.eclipse.persistence.exceptions.DatabaseException;
+import org.eclipse.persistence.internal.jpa.EntityManagerFactoryImpl;
+import org.eclipse.persistence.jpa.JpaEntityManagerFactory;
+import org.eclipse.persistence.testing.framework.ReflectionHelper;
+import org.eclipse.persistence.testing.framework.jpa.junit.JUnitTestCase;
+import org.eclipse.persistence.tools.schemaframework.TableCreator;
+
+/**
+ * Test {@link jakarta.persistence.SchemaManager} implementation in EclipseLink.
+ * This is an abstract class with all common code for child classes with individual tests.
+ * All those tests are based on database schema modifications, so they shall not run in parallel.
+ * Each child class shall contain just a single test.
+ */
+public abstract class AbstractSchemaManager extends JUnitTestCase {
+ private org.eclipse.persistence.tools.schemaframework.SchemaManager schemaManager;
+ JpaEntityManagerFactory emf = null;
+
+ public AbstractSchemaManager() {
+ }
+
+ public AbstractSchemaManager(String name) {
+ super(name);
+ setPuName(getPersistenceUnitName());
+ }
+
+ @Override
+ public String getPersistenceUnitName() {
+ return "persistence32";
+ }
+
+ @Override
+ public void setUp() {
+ super.setUp();
+ emf = getEntityManagerFactory(getPersistenceUnitName())
+ .unwrap(EntityManagerFactoryImpl.class);
+ schemaManager = new org.eclipse.persistence.tools.schemaframework.SchemaManager(emf.getDatabaseSession());
+ dropTables();
+ }
+
+ @Override
+ public void tearDown() {
+ dropTables();
+ }
+
+ /**
+ * Build test suite.
+ * Schema manager suites may contain just a single test.
+ *
+ * @param name name of the suite
+ * @param test test to add to the suite
+ * @return collection of tests to execute
+ */
+ static TestSuite suite(String name, AbstractSchemaManager test) {
+ TestSuite suite = new TestSuite();
+ suite.setName(name);
+ suite.addTest(test);
+ return suite;
+ }
+
+ TableCreator getDefaultTableCreator() {
+ // getDefaultTableCreator has protected access
+ try {
+ return ReflectionHelper.invokeMethod("getDefaultTableCreator",
+ schemaManager,
+ new Class>[] {boolean.class},
+ TableCreator.class,
+ new Object[] {Boolean.TRUE});
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException("Invocation of getDefaultTableCreator failed", e);
+ }
+ }
+
+ void dropTables() {
+ try {
+ schemaManager.dropDefaultTables();
+ } catch (DatabaseException de) {
+ emf.getDatabaseSession().logMessage(de.getLocalizedMessage());
+ }
+ }
+
+ void createTables() {
+ try {
+ schemaManager.createDefaultTables(true);
+ } catch (DatabaseException de) {
+ emf.getDatabaseSession().logMessage(de.getLocalizedMessage());
+ }
+ }
+
+ void checkMissingTable(Set initialMissingTablesSet, Set missingTablesSet, Set checked, String table) {
+ if (missingTablesSet.contains(table)) {
+ missingTablesSet.remove(table);
+ checked.add(table);
+ } else {
+ boolean first = true;
+ StringBuilder sb = new StringBuilder();
+ sb.append('[');
+ for (String missingTable : initialMissingTablesSet) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append(',');
+ }
+ sb.append(missingTable);
+ }
+ sb.append(']');
+ if (checked.contains(table)) {
+ fail(String.format("Duplicate table %s entry was found in expected tables Set %s", table, sb.toString()));
+ } else {
+ fail(String.format("Table %s was not found in expected tables Set %s", table, sb.toString()));
+ }
+ }
+ }
+
+}
diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/AbstractSuite.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/AbstractSuite.java
new file mode 100644
index 00000000000..7382b11f17f
--- /dev/null
+++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/AbstractSuite.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2023 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
+ */
+package org.eclipse.persistence.testing.tests.jpa.persistence32;
+
+import jakarta.persistence.Persistence;
+import junit.framework.TestSuite;
+import org.eclipse.persistence.internal.jpa.EntityManagerFactoryImpl;
+import org.eclipse.persistence.jpa.JpaEntityManagerFactory;
+import org.eclipse.persistence.testing.framework.jpa.junit.JUnitTestCase;
+import org.eclipse.persistence.testing.framework.junit.JUnitTestCaseHelper;
+
+/**
+ * Abstract {@link JUnitTestCase} suite.
+ * Adds {@link #suiteSetUp()} and {@link #suiteTearDown()} methods executed before and after
+ * whole test suite execution.
+ * Contains suite wide {@link jakarta.persistence.EntityManagerFactory} instance initialized
+ * by {@link #getPersistenceUnitName()}.
+ * {@link jakarta.persistence.EntityManagerFactory} and database schema are initialized
+ * in {@link #suiteSetUp()} method. Database schema is dropped in {@link #suiteTearDown()} method.
+ *
+ */
+public abstract class AbstractSuite extends JUnitTestCase {
+
+ // Total number of tests in this suite
+ private static int TEST_COUNT = 0;
+ // Test counter (decreasing from TEST_COUNT to 0) to trigger suiteSetUp/suiteTearDown
+ private static int testCounter;
+
+ // EntityManagerFactory instance shared by the whole suite
+ static JpaEntityManagerFactory emf = null;
+
+ /**
+ * Build test suite.
+ * Adds model test setup as first and model test cleanup as last test
+ * in the returned tests collection.
+ * Using this metod is mandatory for suite creation.
+ *
+ * @param name name of the suite
+ * @param tests tests to add to the suite
+ * @return collection of tests to execute
+ */
+ static TestSuite suite(String name, AbstractSuite... tests) {
+ TestSuite suite = new TestSuite();
+ suite.setName(name);
+ for (AbstractSuite test : tests) {
+ suite.addTest(test);
+ }
+ testCounter = TEST_COUNT = suite.testCount();
+ return suite;
+ }
+
+ /**
+ * Creates an instance of {@link AbstractSuite}.
+ */
+ public AbstractSuite() {
+ super();
+ }
+
+ /**
+ * Creates an instance of {@link AbstractSuite} with custom test case name.
+ *
+ * @param name name of the test case
+ */
+ public AbstractSuite(String name) {
+ super(name);
+ setPuName(getPersistenceUnitName());
+ }
+
+ /**
+ * Initialize the test suite.
+ * This method is being executed before the whole test suite. This method initializes the suite wide
+ * {@link jakarta.persistence.EntityManagerFactory} instance and creates the database schema.
+ * Child class may overwrite this method to do additional initialization but should also call this method too.
+ */
+ protected void suiteSetUp() {
+ emf = Persistence.createEntityManagerFactory(
+ getPersistenceUnitName(),
+ JUnitTestCaseHelper.getDatabaseProperties(getPersistenceUnitName()))
+ .unwrap(EntityManagerFactoryImpl.class);
+ emf.getSchemaManager().create(true);
+ }
+
+ /**
+ * Clean up the test suite.
+ * This method is being executed after the whole test suite. This method drops the database schema
+ * and closes the suite wide {@link jakarta.persistence.EntityManagerFactory} instance.
+ * Child class may overwrite this method to do additional cleanup but should also call this method too.
+ */
+ protected void suiteTearDown() {
+ emf.getSchemaManager().drop(true);
+ emf.close();
+ }
+
+ /**
+ * This method is called before a test is executed.
+ * This method implements {@link #suiteSetUp()} call, so it shall be called by child class if overwritten.
+ */
+ @Override
+ public void setUp() {
+ super.setUp();
+ if (testCounter == TEST_COUNT) {
+ suiteSetUp();
+ }
+ testCounter--;
+ }
+
+ /**
+ * This method is called after a test is executed.
+ * This method implements {@link #suiteTearDown()} call, so it shall be called by child class if overwritten.
+ */
+ @Override
+ public void tearDown() {
+ super.tearDown();
+ if (testCounter == 0) {
+ suiteTearDown();
+ }
+ }
+
+ @Override
+ public void clearCache() {
+ emf.getCache().evictAll();
+ super.clearCache();
+ }
+
+}
diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/CriteriaBuilderTest.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/CriteriaBuilderTest.java
index 6c0e1bf0b16..b08dc900998 100644
--- a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/CriteriaBuilderTest.java
+++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/CriteriaBuilderTest.java
@@ -30,14 +30,12 @@
import jakarta.persistence.criteria.ParameterExpression;
import jakarta.persistence.criteria.Root;
import junit.framework.Test;
-import junit.framework.TestSuite;
-import org.eclipse.persistence.internal.jpa.EntityManagerFactoryImpl;
-import org.eclipse.persistence.jpa.JpaEntityManagerFactory;
-import org.eclipse.persistence.testing.framework.jpa.junit.JUnitTestCase;
-import org.eclipse.persistence.testing.models.jpa.persistence32.Persistence32TableCreator;
import org.eclipse.persistence.testing.models.jpa.persistence32.SyntaxEntity;
-public class CriteriaBuilderTest extends JUnitTestCase {
+/**
+ * Verify jakarta.persistence 3.2 API changes in {@link CriteriaBuilder}.
+ */
+public class CriteriaBuilderTest extends AbstractSuite {
// SyntaxEntity instances, array index is equal to ID
// Values must be unique to get just single result and verify it by ID
@@ -50,47 +48,43 @@ public class CriteriaBuilderTest extends JUnitTestCase {
new SyntaxEntity(5L, null, null, null, null, LocalDate.of(1918, 9, 28), null)
};
- private static final int ENTITIES_COUNT = ENTITIES.length - 1;
-
- private JpaEntityManagerFactory emf = null;
-
public static Test suite() {
- TestSuite suite = new TestSuite();
- suite.setName("CriteriaBuilderTests");
- suite.addTest(new CriteriaBuilderTest("testSetup"));
- suite.addTest(new CriteriaBuilderTest("testAndPredicateAsListOf0"));
- suite.addTest(new CriteriaBuilderTest("testOrPredicateAsListOf0"));
- suite.addTest(new CriteriaBuilderTest("testAndPredicateAsListOf1"));
- suite.addTest(new CriteriaBuilderTest("testOrPredicateAsListOf1"));
- suite.addTest(new CriteriaBuilderTest("testAndPredicateAsListOf2"));
- suite.addTest(new CriteriaBuilderTest("testOrPredicateAsListOf2"));
- suite.addTest(new CriteriaBuilderTest("testAndPredicateAsListOfN"));
- suite.addTest(new CriteriaBuilderTest("testOrPredicateAsListOfN"));
- suite.addTest(new CriteriaBuilderTest("testLeftIntLen"));
- suite.addTest(new CriteriaBuilderTest("testLeftExprLen"));
- suite.addTest(new CriteriaBuilderTest("testRightIntLen"));
- suite.addTest(new CriteriaBuilderTest("testRightExprLen"));
- suite.addTest(new CriteriaBuilderTest("testReplaceExprExpr"));
- suite.addTest(new CriteriaBuilderTest("testReplaceExprStr"));
- suite.addTest(new CriteriaBuilderTest("testReplaceStrExpr"));
- suite.addTest(new CriteriaBuilderTest("testReplaceStrStr"));
- suite.addTest(new CriteriaBuilderTest("testExtractHourFromTime"));
- suite.addTest(new CriteriaBuilderTest("testExtractMinuteFromTime"));
- suite.addTest(new CriteriaBuilderTest("testExtractSecondFromTime"));
- suite.addTest(new CriteriaBuilderTest("testExtractYearFromDate"));
- suite.addTest(new CriteriaBuilderTest("testExtractMonthFromDate"));
- suite.addTest(new CriteriaBuilderTest("testExtractDayFromDate"));
- suite.addTest(new CriteriaBuilderTest("testExtractQuarterFromDate"));
- suite.addTest(new CriteriaBuilderTest("testExtractWeekFromDate"));
- suite.addTest(new CriteriaBuilderTest("testExpressionEqualToExpression"));
- suite.addTest(new CriteriaBuilderTest("testExpressionEqualToObject"));
- suite.addTest(new CriteriaBuilderTest("testExpressionNotEqualToExpression"));
- suite.addTest(new CriteriaBuilderTest("testExpressionNotEqualToObject"));
- suite.addTest(new CriteriaBuilderTest("testExpressionCast"));
- return suite;
+ return suite(
+ "CriteriaBuilderTests",
+ new CriteriaBuilderTest("testAndPredicateAsListOf0"),
+ new CriteriaBuilderTest("testOrPredicateAsListOf0"),
+ new CriteriaBuilderTest("testAndPredicateAsListOf1"),
+ new CriteriaBuilderTest("testOrPredicateAsListOf1"),
+ new CriteriaBuilderTest("testAndPredicateAsListOf2"),
+ new CriteriaBuilderTest("testOrPredicateAsListOf2"),
+ new CriteriaBuilderTest("testAndPredicateAsListOfN"),
+ new CriteriaBuilderTest("testOrPredicateAsListOfN"),
+ new CriteriaBuilderTest("testLeftIntLen"),
+ new CriteriaBuilderTest("testLeftExprLen"),
+ new CriteriaBuilderTest("testRightIntLen"),
+ new CriteriaBuilderTest("testRightExprLen"),
+ new CriteriaBuilderTest("testReplaceExprExpr"),
+ new CriteriaBuilderTest("testReplaceExprStr"),
+ new CriteriaBuilderTest("testReplaceStrExpr"),
+ new CriteriaBuilderTest("testReplaceStrStr"),
+ new CriteriaBuilderTest("testExtractHourFromTime"),
+ new CriteriaBuilderTest("testExtractMinuteFromTime"),
+ new CriteriaBuilderTest("testExtractSecondFromTime"),
+ new CriteriaBuilderTest("testExtractYearFromDate"),
+ new CriteriaBuilderTest("testExtractMonthFromDate"),
+ new CriteriaBuilderTest("testExtractDayFromDate"),
+ new CriteriaBuilderTest("testExtractQuarterFromDate"),
+ new CriteriaBuilderTest("testExtractWeekFromDate"),
+ new CriteriaBuilderTest("testExpressionEqualToExpression"),
+ new CriteriaBuilderTest("testExpressionEqualToObject"),
+ new CriteriaBuilderTest("testExpressionNotEqualToExpression"),
+ new CriteriaBuilderTest("testExpressionNotEqualToObject"),
+ new CriteriaBuilderTest("testExpressionCast")
+ );
}
public CriteriaBuilderTest() {
+ super();
}
public CriteriaBuilderTest(String name) {
@@ -104,18 +98,8 @@ public String getPersistenceUnitName() {
}
@Override
- public void setUp() {
- super.setUp();
- emf = getEntityManagerFactory(getPersistenceUnitName()).unwrap(EntityManagerFactoryImpl.class);
- }
-
- /**
- * The setup is done as a test, both to record its failure, and to allow
- * execution in the server.
- */
- public void testSetup() {
- new Persistence32TableCreator().replaceTables(JUnitTestCase.getServerSession(getPersistenceUnitName()));
- clearCache();
+ protected void suiteSetUp() {
+ super.suiteSetUp();
try (EntityManager em = emf.createEntityManager()) {
EntityTransaction et = em.getTransaction();
try {
diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/DatabaseActionVerifyTest.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/DatabaseActionVerifyTest.java
new file mode 100644
index 00000000000..445499c1edf
--- /dev/null
+++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/DatabaseActionVerifyTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2023 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
+ */
+package org.eclipse.persistence.testing.tests.jpa.persistence32;
+
+import jakarta.persistence.Persistence;
+import junit.framework.Test;
+import org.eclipse.persistence.internal.jpa.EntityManagerFactoryImpl;
+import org.eclipse.persistence.testing.framework.junit.JUnitTestCaseHelper;
+
+/**
+ * Verify jakarta.persistence 3.2 changes in {@code jakarta.persistence.schema-generation.database.action} property.
+ */
+public class DatabaseActionVerifyTest extends AbstractSuite {
+
+ public static Test suite() {
+ return suite(
+ "ScriptsActionVerify",
+ new DatabaseActionVerifyTest("testVerify")
+ );
+ }
+
+ public DatabaseActionVerifyTest() {
+ super();
+ }
+
+ public DatabaseActionVerifyTest(String name) {
+ super(name);
+ setPuName(getPersistenceUnitName());
+ }
+
+ @Override
+ public String getPersistenceUnitName() {
+ return "persistence32";
+ }
+
+ // Create EntityManagerFactory with "jakarta.persistence.schema-generation.database.action" set to "verify"
+ // Schema exists so validation shall pass.
+ public void testVerify() {
+ String persistenceUnitName = "persistence32_schemagen_verify";
+ Persistence.createEntityManagerFactory(
+ persistenceUnitName,
+ JUnitTestCaseHelper.getDatabaseProperties(persistenceUnitName))
+ .unwrap(EntityManagerFactoryImpl.class);
+ }
+
+}
diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/EntityManagerFactoryTest.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/EntityManagerFactoryTest.java
index dd5e7f3925b..a04c11817d7 100644
--- a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/EntityManagerFactoryTest.java
+++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/EntityManagerFactoryTest.java
@@ -37,6 +37,9 @@
import org.eclipse.persistence.testing.models.jpa.persistence32.Trainer_;
import org.eclipse.persistence.testing.models.jpa.persistence32.Type;
+/**
+ * Verify jakarta.persistence 3.2 API changes in {@link jakarta.persistence.EntityManagerFactory}.
+ */
public class EntityManagerFactoryTest extends AbstractPokemon {
public static Test suite() {
diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/QueryTest.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/QueryTest.java
index b4924dd83be..639fffa6963 100644
--- a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/QueryTest.java
+++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/QueryTest.java
@@ -20,6 +20,9 @@
import org.eclipse.persistence.testing.models.jpa.persistence32.Pokemon;
import org.junit.Assert;
+/**
+ * Verify jakarta.persistence 3.2 API changes in queries.
+ */
public class QueryTest extends AbstractPokemon {
// Pokemons. Array index is ID value.
@@ -35,7 +38,6 @@ public class QueryTest extends AbstractPokemon {
public static Test suite() {
return suite(
"QueryTest",
- new QueryTest("testSetup"),
new QueryTest("testGetSingleResultWithEmptyResult"),
new QueryTest("testGetSingleResultWithSingleResult"),
new QueryTest("testGetSingleResultWithMultipleResults"),
@@ -55,11 +57,10 @@ public QueryTest(String name) {
setPuName(getPersistenceUnitName());
}
- /**
- * The setup is done as a test, both to record its failure, and to allow
- * execution in the server.
- */
- public void testSetup() {
+ // Initialize data
+ @Override
+ protected void suiteSetUp() {
+ super.suiteSetUp();
emf.runInTransaction(em -> {
for (int i = 1; i < POKEMONS.length; i++) {
em.persist(POKEMONS[i]);
diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/SchemaManagerCreateTest.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/SchemaManagerCreateTest.java
new file mode 100644
index 00000000000..42ff3570232
--- /dev/null
+++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/SchemaManagerCreateTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2023 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
+ */
+package org.eclipse.persistence.testing.tests.jpa.persistence32;
+
+import jakarta.persistence.SchemaManager;
+import junit.framework.Test;
+
+import static org.eclipse.persistence.testing.tests.jpa.persistence32.AbstractPokemon.TEAMS;
+import static org.eclipse.persistence.testing.tests.jpa.persistence32.AbstractPokemon.TRAINERS;
+import static org.eclipse.persistence.testing.tests.jpa.persistence32.AbstractPokemon.TYPES;
+
+/**
+ * Verify jakarta.persistence 3.2 API changes in {@link SchemaManager}.
+ * Test {@link SchemaManager#create(boolean)} method on database with no schema.
+ */
+public class SchemaManagerCreateTest extends AbstractSchemaManager {
+
+ public static Test suite() {
+ return suite(
+ "SchemaManagerCreateTest",
+ new SchemaManagerCreateTest("testCreate")
+ );
+ }
+ public SchemaManagerCreateTest() {
+ }
+
+ public SchemaManagerCreateTest(String name) {
+ super(name);
+ }
+
+ // Test SchemaManager create method
+ public void testCreate() {
+ // Tables are always dropped in setUp() method
+ // Create the schema
+ SchemaManager schemaManager = emf.getSchemaManager();
+ schemaManager.create(true);
+ // Try to store data into the schema
+ emf.runInTransaction(em -> {
+ for (int i = 1; i < TEAMS.length; i++) {
+ em.persist(TEAMS[i]);
+ }
+ for (int i = 1; i < TRAINERS.length; i++) {
+ em.persist(TRAINERS[i]);
+ }
+ for (int i = 1; i < TYPES.length; i++) {
+ em.persist(TYPES[i]);
+ }
+ });
+ }
+
+
+
+}
diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/SchemaManagerDropTest.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/SchemaManagerDropTest.java
new file mode 100644
index 00000000000..86b2d08c4b2
--- /dev/null
+++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/SchemaManagerDropTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2023 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
+ */
+package org.eclipse.persistence.testing.tests.jpa.persistence32;
+
+import jakarta.persistence.PersistenceException;
+import jakarta.persistence.SchemaManager;
+import junit.framework.Test;
+import org.eclipse.persistence.logging.SessionLog;
+
+import static org.eclipse.persistence.testing.tests.jpa.persistence32.AbstractPokemon.TEAMS;
+import static org.eclipse.persistence.testing.tests.jpa.persistence32.AbstractPokemon.TRAINERS;
+import static org.eclipse.persistence.testing.tests.jpa.persistence32.AbstractPokemon.TYPES;
+
+/**
+ * Verify jakarta.persistence 3.2 API changes in {@link SchemaManager}.
+ * Test {@link SchemaManager#drop(boolean)} method on database with already existing schema.
+ */
+public class SchemaManagerDropTest extends AbstractSchemaManager {
+
+ public static Test suite() {
+ return suite(
+ "SchemaManagerDropTest",
+ new SchemaManagerDropTest("testDrop")
+ );
+ }
+ public SchemaManagerDropTest() {
+ }
+
+ public SchemaManagerDropTest(String name) {
+ super(name);
+ }
+
+ // Test SchemaManager drop method
+ public void testDrop() {
+ // Make sure that tables exist before being dropped
+ createTables();
+ // ...and persist call works
+ emf.runInTransaction(em -> {
+ for (int i = 1; i < TEAMS.length; i++) {
+ em.persist(TEAMS[i]);
+ }
+ for (int i = 1; i < TRAINERS.length; i++) {
+ em.persist(TRAINERS[i]);
+ }
+ for (int i = 1; i < TYPES.length; i++) {
+ em.persist(TYPES[i]);
+ }
+ });
+ // Drop the schema
+ SchemaManager schemaManager = emf.getSchemaManager();
+ schemaManager.drop(true);
+ // Turn off logging to suppress expected SQL errors warnings
+ int logLevel = emf.getDatabaseSession().getSessionLog().getLevel();
+ emf.getDatabaseSession().getSessionLog().setLevel(SessionLog.OFF);
+ // Verify that any attempt to store data throws an exception because of missing tables
+ // - Team entity
+ try {
+ emf.runInTransaction(em -> {
+ for (int i = 1; i < TEAMS.length; i++) {
+ em.persist(TEAMS[i]);
+ }
+ });
+ fail("Calling persist on entity after database schema was deleted shall throw an exception.");
+ } catch (PersistenceException pe) {
+ // Silently ignore PersistenceException
+ }
+ // - Trainer entity
+ try {
+ emf.runInTransaction(em -> {
+ for (int i = 1; i < TRAINERS.length; i++) {
+ em.persist(TRAINERS[i]);
+ }
+ });
+ fail("Calling persist on entity after database schema was deleted shall throw an exception.");
+ } catch (PersistenceException pe) {
+ // Silently ignore PersistenceException
+ }
+ // - Type entity
+ try {
+ emf.runInTransaction(em -> {
+ for (int i = 1; i < TYPES.length; i++) {
+ em.persist(TYPES[i]);
+ }
+ });
+ fail("Calling persist on entity after database schema was deleted shall throw an exception.");
+ } catch (PersistenceException pe) {
+ // Silently ignore PersistenceException
+ }
+ emf.getDatabaseSession().getSessionLog().setLevel(logLevel);
+ }
+
+}
diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/SchemaManagerTruncateOnExistingTest.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/SchemaManagerTruncateOnExistingTest.java
new file mode 100644
index 00000000000..4e45f696c07
--- /dev/null
+++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/SchemaManagerTruncateOnExistingTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2023 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
+ */
+package org.eclipse.persistence.testing.tests.jpa.persistence32;
+
+import jakarta.persistence.SchemaManager;
+import junit.framework.Test;
+
+import static org.eclipse.persistence.testing.tests.jpa.persistence32.AbstractPokemon.TEAMS;
+import static org.eclipse.persistence.testing.tests.jpa.persistence32.AbstractPokemon.TRAINERS;
+import static org.eclipse.persistence.testing.tests.jpa.persistence32.AbstractPokemon.TYPES;
+
+/**
+ * Verify jakarta.persistence 3.2 API changes in {@link SchemaManager}.
+ * Test {@link SchemaManager#truncate()} method on database with already existing schema and data.
+ */
+public class SchemaManagerTruncateOnExistingTest extends AbstractSchemaManager {
+
+ public static Test suite() {
+ return suite(
+ "SchemaManagerTruncateOnExistingTest",
+ new SchemaManagerTruncateOnExistingTest("testTruncateOnExistingSchema")
+ );
+ }
+ public SchemaManagerTruncateOnExistingTest() {
+ }
+
+ public SchemaManagerTruncateOnExistingTest(String name) {
+ super(name);
+ }
+
+ // Test SchemaManager truncate method
+ public void testTruncateOnExistingSchema() {
+ // Tables are always dropped in setUp() method
+ // Make sure that tables exist and contain data
+ createTables();
+ emf.runInTransaction(em -> {
+ for (int i = 1; i < TEAMS.length; i++) {
+ em.persist(TEAMS[i]);
+ }
+ for (int i = 1; i < TRAINERS.length; i++) {
+ em.persist(TRAINERS[i]);
+ }
+ for (int i = 1; i < TYPES.length; i++) {
+ em.persist(TYPES[i]);
+ }
+ });
+ // Truncate the schema
+ SchemaManager schemaManager = emf.getSchemaManager();
+ schemaManager.truncate();
+ // Verify that tables still exist but are empty
+ // - Team count shall be 0
+ int teamCount = emf.callInTransaction(
+ em -> em.createQuery("SELECT count(t) FROM Team t", Integer.class).getFirstResult());
+ assertEquals(teamCount, 0);
+ // - Trainer count shall be 0
+ int trainerCount = emf.callInTransaction(
+ em -> em.createQuery("SELECT count(t) FROM Trainer t", Integer.class).getFirstResult());
+ assertEquals(trainerCount, 0);
+ // - Type count shall be 0
+ int typeCount = emf.callInTransaction(
+ em -> em.createQuery("SELECT count(t) FROM Type t", Integer.class).getFirstResult());
+ assertEquals(typeCount, 0);
+ }
+
+}
diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/SchemaManagerValidateOnMissingColumnTest.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/SchemaManagerValidateOnMissingColumnTest.java
new file mode 100644
index 00000000000..3ed41d7a19b
--- /dev/null
+++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/SchemaManagerValidateOnMissingColumnTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2023 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
+ */
+package org.eclipse.persistence.testing.tests.jpa.persistence32;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import jakarta.persistence.SchemaManager;
+import jakarta.persistence.SchemaValidationException;
+import junit.framework.Test;
+import org.eclipse.persistence.tools.schemaframework.TableCreator;
+import org.eclipse.persistence.tools.schemaframework.TableDefinition;
+import org.eclipse.persistence.tools.schemaframework.TableValidationException;
+
+/**
+ * Verify jakarta.persistence 3.2 API changes in {@link SchemaManager}.
+ * Test {@link jakarta.persistence.SchemaManager#validate()} method on database with existing but modified schema.
+ */
+public class SchemaManagerValidateOnMissingColumnTest extends AbstractSchemaManager {
+
+ public static Test suite() {
+ return suite(
+ "SchemaManagerValidateOnMissingColumnTest",
+ new SchemaManagerValidateOnMissingColumnTest("testValidateOnMissingColumn")
+ );
+ }
+ public SchemaManagerValidateOnMissingColumnTest() {
+ }
+
+ public SchemaManagerValidateOnMissingColumnTest(String name) {
+ super(name);
+ }
+
+ // Test SchemaManager validate method on existing valid schema
+ public void testValidateOnMissingColumn() {
+ // Make sure that tables exist
+ createTables();
+ // Modify current schema
+ TableCreator tableCreator = getDefaultTableCreator();
+ Map tableDefinitions = new HashMap<>(tableCreator.getTableDefinitions().size());
+ for (TableDefinition tableDefinition : tableCreator.getTableDefinitions()) {
+ String tableName = tableDefinition.getTable() == null
+ ? tableDefinition.getName()
+ : tableDefinition.getTable().getName();
+ tableDefinitions.put(tableName, tableDefinition);
+ }
+ // Remove "NAME" field from "PERSISTENCE32_TEAM"
+ TableDefinition team = tableDefinitions.get("PERSISTENCE32_TEAM");
+ team.dropFieldOnDatabase(emf.getDatabaseSession(), "NAME");
+ // Do the validation
+ SchemaManager schemaManager = emf.getSchemaManager();
+ try {
+ // Test validation
+ schemaManager.validate();
+ fail("Schema validation shall throw an exception on missing column");
+ } catch (SchemaValidationException sve) {
+ // Validation is expected to fail and return all missing columns
+ Exception[] exceptions = sve.getFailures();
+ Map> missingColumns = Map.of("PERSISTENCE32_TEAM", Set.of("NAME"));
+ for (TableValidationException exception : (TableValidationException[]) exceptions) {
+ if (!(exception instanceof TableValidationException.MissingColumns)) {
+ fail("Exception is not an instance of TableValidationException.MissingColumns");
+ }
+ if (exception.getFailure() == TableValidationException.ValidationFailure.MISSING_COLUMNS) {
+ assertEquals("PERSISTENCE32_TEAM", exception.getTable());
+
+ } else {
+ fail("Exception type is not MISSING_COLUMNS");
+ }
+ checkMissingColumns(missingColumns,
+ exception.getTable(),
+ exception.unwrap(TableValidationException.MissingColumns.class)
+ .getColumns()
+ .stream()
+ .map(String::toUpperCase)
+ .toList());
+ }
+ }
+ }
+
+ private void checkMissingColumns(Map> missingColumns, String table, List columns) {
+ Set columnsToCheck = new HashSet<>(missingColumns.get(table));
+ Set checked = new HashSet<>(columnsToCheck.size());
+ if (columnsToCheck.isEmpty()) {
+ fail(String.format("Table %s is not expected to have missing columns", table));
+ }
+ for (String column : columns) {
+ if (columnsToCheck.contains(column)) {
+ columnsToCheck.remove(column);
+ checked.add(column);
+ } else {
+ if (checked.contains(column)) {
+ fail(String.format("Duplicate missing %s column entry for table %s", column, table));
+ } else {
+ fail(String.format("Missing column %s was not reported for table %s", column, table));
+ }
+ }
+ }
+ }
+
+}
diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/SchemaManagerValidateOnMissingSchemaTest.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/SchemaManagerValidateOnMissingSchemaTest.java
new file mode 100644
index 00000000000..6465397c2d7
--- /dev/null
+++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/SchemaManagerValidateOnMissingSchemaTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2023 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
+ */
+package org.eclipse.persistence.testing.tests.jpa.persistence32;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import jakarta.persistence.SchemaManager;
+import jakarta.persistence.SchemaValidationException;
+import junit.framework.Test;
+import org.eclipse.persistence.tools.schemaframework.TableValidationException;
+
+/**
+ * Verify jakarta.persistence 3.2 API changes in {@link SchemaManager}.
+ * Test {@link SchemaManager#validate()} method on database with missing schema.
+ */
+public class SchemaManagerValidateOnMissingSchemaTest extends AbstractSchemaManager {
+
+ public static Test suite() {
+ return suite(
+ "SchemaManagerValidateOnMissingSchemaTest",
+ new SchemaManagerValidateOnMissingSchemaTest("testValidateOnMissingSchema")
+ );
+ }
+ public SchemaManagerValidateOnMissingSchemaTest() {
+ }
+
+ public SchemaManagerValidateOnMissingSchemaTest(String name) {
+ super(name);
+ }
+
+ // Test SchemaManager validate method on missing schema
+ public void testValidateOnMissingSchema() {
+ // Tables are always dropped in setUp() method
+ SchemaManager schemaManager = emf.getSchemaManager();
+ try {
+ // Test validation
+ schemaManager.validate();
+ fail("Schema validation shall throw an exception on missing schema");
+ } catch (SchemaValidationException sve) {
+ // Validation is expected to fail and return all tables as missing
+ Exception[] exceptions = sve.getFailures();
+ String[] missingTables = new String[] {
+ "PERSISTENCE32_TEAM",
+ "PERSISTENCE32_TRAINER",
+ "PERSISTENCE32_TYPE",
+ "PERSISTENCE32_POKEMON",
+ "PERSISTENCE32_POKEMON_TYPE",
+ "PERSISTENCE32_SYNTAX_ENTITY",
+ "PERSISTENCE32_SE_COLTABLE"
+ };
+ Set missingTablesSet = new HashSet<>(Arrays.asList(missingTables));
+ Set initialMissingTablesSet = Set.copyOf(missingTablesSet);
+ Set checked = new HashSet<>(initialMissingTablesSet.size());
+ for (TableValidationException exception : (TableValidationException[]) exceptions) {
+ if (!(exception instanceof TableValidationException.MissingTable)) {
+ fail("Exception is not an instance of TableValidationException.MissingTable");
+ }
+ if (exception.getFailure() != TableValidationException.ValidationFailure.MISSING_TABLE) {
+ fail("Exception type is not MISSING_TABLE");
+ }
+ checkMissingTable(initialMissingTablesSet, missingTablesSet, checked, exception.getTable());
+ }
+ }
+ }
+
+}
diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/SchemaManagerValidateOnModifiedColumnTest.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/SchemaManagerValidateOnModifiedColumnTest.java
new file mode 100644
index 00000000000..bf8c7dac11b
--- /dev/null
+++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/SchemaManagerValidateOnModifiedColumnTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2023 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
+ */
+package org.eclipse.persistence.testing.tests.jpa.persistence32;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import jakarta.persistence.SchemaManager;
+import jakarta.persistence.SchemaValidationException;
+import junit.framework.Test;
+import org.eclipse.persistence.tools.schemaframework.FieldDefinition;
+import org.eclipse.persistence.tools.schemaframework.TableCreator;
+import org.eclipse.persistence.tools.schemaframework.TableDefinition;
+import org.eclipse.persistence.tools.schemaframework.TableValidationException;
+
+/**
+ * Verify jakarta.persistence 3.2 API changes in {@link SchemaManager}.
+ * Test {@link jakarta.persistence.SchemaManager#validate()} method on database with existing but modified schema.
+ */
+public class SchemaManagerValidateOnModifiedColumnTest extends AbstractSchemaManager {
+
+ public static Test suite() {
+ return suite(
+ "SchemaManagerValidateOnModifiedColumnTest",
+ new SchemaManagerValidateOnModifiedColumnTest("testValidateOnModifiedSchema")
+ );
+ }
+ public SchemaManagerValidateOnModifiedColumnTest() {
+ }
+
+ public SchemaManagerValidateOnModifiedColumnTest(String name) {
+ super(name);
+ }
+
+ // Test SchemaManager validate method on existing valid schema
+ public void testValidateOnModifiedSchema() {
+ // Make sure that tables exist
+ createTables();
+ // Modify current schema
+ TableCreator tableCreator = getDefaultTableCreator();
+ Map tableDefinitions = new HashMap<>(tableCreator.getTableDefinitions().size());
+ for (TableDefinition tableDefinition : tableCreator.getTableDefinitions()) {
+ String tableName = tableDefinition.getTable() == null
+ ? tableDefinition.getName()
+ : tableDefinition.getTable().getName();
+ tableDefinitions.put(tableName, tableDefinition);
+ }
+ // Modify "NAME" field in "PERSISTENCE32_TRAINER"
+ TableDefinition trainer = tableDefinitions.get("PERSISTENCE32_TRAINER");
+ FieldDefinition nameField = trainer.getField("NAME");
+ trainer.dropFieldOnDatabase(emf.getDatabaseSession(), "NAME");
+ FieldDefinition newNameField = new FieldDefinition();
+ newNameField.setName("NAME");
+ // Different type
+ newNameField.setTypeName("NUMERIC");
+ // Different type size
+ newNameField.setSize(nameField.getSize()+5);
+ // Different nullable
+ newNameField.setShouldAllowNull(nameField.shouldAllowNull());
+ newNameField.setIsPrimaryKey(nameField.isPrimaryKey());
+ newNameField.setUnique(nameField.isUnique());
+ newNameField.setIsIdentity(nameField.isIdentity());
+ trainer.addFieldOnDatabase(emf.getDatabaseSession(), newNameField);
+ // Do the validation
+ SchemaManager schemaManager = emf.getSchemaManager();
+ try {
+ // Test validation
+ schemaManager.validate();
+ fail("Schema validation shall throw an exception on modified column");
+ } catch (SchemaValidationException sve) {
+ // Validation is expected to fail and return all missing columns
+ Exception[] exceptions = sve.getFailures();
+ for (TableValidationException exception : (TableValidationException[]) exceptions) {
+ if (!(exception instanceof TableValidationException.DifferentColumns)) {
+ fail("Exception is not an instance of TableValidationException.DifferentColumns");
+ }
+ List diffs
+ = exception.unwrap(TableValidationException.DifferentColumns.class).getDifferences();
+ assertEquals(1, diffs.size());
+ for (TableValidationException.DifferentColumns.Difference diff : diffs) {
+ assertEquals("PERSISTENCE32_TRAINER", exception.getTable());
+ assertEquals(TableValidationException.DifferentColumns.Type.TYPE_DIFFERENCE, diff.getType());
+ TableValidationException.DifferentColumns.TypeDifference typeDiff = diff.unwrap(TableValidationException.DifferentColumns.TypeDifference.class);
+ // Type names are database specific so let's just check that they are not the same
+ assertNotSame(typeDiff.getModelValue(), typeDiff.getDbValue());
+ }
+ }
+ }
+ }
+
+}
diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/SchemaManagerValidateOnSurplusColumnTest.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/SchemaManagerValidateOnSurplusColumnTest.java
new file mode 100644
index 00000000000..40d523a8755
--- /dev/null
+++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/SchemaManagerValidateOnSurplusColumnTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2023 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
+ */
+package org.eclipse.persistence.testing.tests.jpa.persistence32;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import jakarta.persistence.SchemaManager;
+import jakarta.persistence.SchemaValidationException;
+import junit.framework.Test;
+import org.eclipse.persistence.tools.schemaframework.FieldDefinition;
+import org.eclipse.persistence.tools.schemaframework.TableCreator;
+import org.eclipse.persistence.tools.schemaframework.TableDefinition;
+import org.eclipse.persistence.tools.schemaframework.TableValidationException;
+
+/**
+ * Verify jakarta.persistence 3.2 API changes in {@link SchemaManager}.
+ * Test {@link jakarta.persistence.SchemaManager#validate()} method on database with existing but modified schema.
+ */
+public class SchemaManagerValidateOnSurplusColumnTest extends AbstractSchemaManager {
+
+ public static Test suite() {
+ return suite(
+ "SchemaManagerValidateOnSurplusColumnTest",
+ new SchemaManagerValidateOnSurplusColumnTest("testValidateOnModifiedSchema")
+ );
+ }
+ public SchemaManagerValidateOnSurplusColumnTest() {
+ }
+
+ public SchemaManagerValidateOnSurplusColumnTest(String name) {
+ super(name);
+ }
+
+ // Test SchemaManager validate method on existing valid schema
+ public void testValidateOnModifiedSchema() {
+ // Make sure that tables exist
+ createTables();
+ // Modify current schema
+ TableCreator tableCreator = getDefaultTableCreator();
+ Map tableDefinitions = new HashMap<>(tableCreator.getTableDefinitions().size());
+ for (TableDefinition tableDefinition : tableCreator.getTableDefinitions()) {
+ String tableName = tableDefinition.getTable() == null
+ ? tableDefinition.getName()
+ : tableDefinition.getTable().getName();
+ tableDefinitions.put(tableName, tableDefinition);
+ }
+ // Extend PERSISTENCE32_TRAINER with age field
+ TableDefinition trainer = tableDefinitions.get("PERSISTENCE32_TRAINER");
+ FieldDefinition ageField = new FieldDefinition();
+ ageField.setName("age");
+ ageField.setTypeName("NUMERIC");
+ ageField.setSize(15);
+ ageField.setShouldAllowNull(true);
+ ageField.setIsPrimaryKey(false);
+ ageField.setUnique(false);
+ ageField.setIsIdentity(false);
+ trainer.addFieldOnDatabase(emf.getDatabaseSession(), ageField);
+ // Do the validation
+ SchemaManager schemaManager = emf.getSchemaManager();
+ try {
+ // Test validation
+ schemaManager.validate();
+ } catch (SchemaValidationException sve) {
+ // Validation is expected to fail and return all missing columns
+ Exception[] exceptions = sve.getFailures();
+ Map> surplusColumns = Map.of("PERSISTENCE32_TRAINER", Set.of("AGE"));
+ for (TableValidationException exception : (TableValidationException[]) exceptions) {
+ if (!(exception instanceof TableValidationException.SurplusColumns)) {
+ fail("Exception is not an instance of TableValidationException.SurplusColumns");
+ }
+ if (exception.getFailure() == TableValidationException.ValidationFailure.SURPLUS_COLUMNS) {
+ assertEquals("PERSISTENCE32_TRAINER", exception.getTable());
+ } else {
+ fail("Exception type is not SURPLUS_COLUMNS");
+ }
+ checkSurplusColumns(surplusColumns,
+ exception.getTable(),
+ exception.unwrap(TableValidationException.SurplusColumns.class)
+ .getColumns()
+ .stream()
+ .map(String::toUpperCase)
+ .toList());
+ }
+ }
+ }
+
+ private void checkSurplusColumns(Map> surplusColumns, String table, List columns) {
+ Set columnsToCheck = new HashSet<>(surplusColumns.get(table));
+ Set checked = new HashSet<>(columnsToCheck.size());
+ if (columnsToCheck.isEmpty()) {
+ fail(String.format("Table %s is not expected to have missing columns", table));
+ }
+ for (String column : columns) {
+ if (columnsToCheck.contains(column)) {
+ columnsToCheck.remove(column);
+ checked.add(column);
+ } else {
+ if (checked.contains(column)) {
+ fail(String.format("Duplicate missing %s column entry for table %s", column, table));
+ } else {
+ fail(String.format("Missing column %s was not reported for table %s", column, table));
+ }
+ }
+ }
+ }
+
+}
diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/SchemaManagerValidateOnValidSchemaTest.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/SchemaManagerValidateOnValidSchemaTest.java
new file mode 100644
index 00000000000..f5573e7396b
--- /dev/null
+++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/SchemaManagerValidateOnValidSchemaTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2023 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
+ */
+package org.eclipse.persistence.testing.tests.jpa.persistence32;
+
+import jakarta.persistence.SchemaManager;
+import jakarta.persistence.SchemaValidationException;
+import junit.framework.Test;
+
+/**
+ * Verify jakarta.persistence 3.2 API changes in {@link SchemaManager}.
+ * Test {@link SchemaManager#validate()} method on database with existing and valid schema.
+ */
+public class SchemaManagerValidateOnValidSchemaTest extends AbstractSchemaManager {
+
+ public static Test suite() {
+ return suite(
+ "SchemaManagerValidateOnValidSchemaTest",
+ new SchemaManagerValidateOnValidSchemaTest("testValidateOnValidSchema")
+ );
+ }
+ public SchemaManagerValidateOnValidSchemaTest() {
+ }
+
+ public SchemaManagerValidateOnValidSchemaTest(String name) {
+ super(name);
+ }
+
+ // Test SchemaManager validate method on existing valid schema
+ public void testValidateOnValidSchema() {
+ // Make sure that tables exist
+ createTables();
+ SchemaManager schemaManager = emf.getSchemaManager();
+ try {
+ // Test validation
+ schemaManager.validate();
+ } catch (SchemaValidationException sve) {
+ fail(sve.getLocalizedMessage());
+ }
+ }
+
+}
diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/UnionCriteriaQueryTest.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/UnionCriteriaQueryTest.java
index 96bd487c21c..6739fb2bd35 100644
--- a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/UnionCriteriaQueryTest.java
+++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/UnionCriteriaQueryTest.java
@@ -30,6 +30,9 @@
import org.eclipse.persistence.testing.models.jpa.persistence32.Pokemon;
import org.eclipse.persistence.testing.models.jpa.persistence32.Trainer;
+/**
+ * Verify jakarta.persistence 3.2 API changes in {@link CriteriaBuilder} for union expressions.
+ */
public class UnionCriteriaQueryTest extends AbstractPokemon {
// Pokemons. Array index is ID value.
@@ -50,7 +53,6 @@ public class UnionCriteriaQueryTest extends AbstractPokemon {
public static Test suite() {
return suite(
"QueryTest",
- new UnionCriteriaQueryTest("testSetup"),
new UnionCriteriaQueryTest("testUnionWithNoSelection"),
new UnionCriteriaQueryTest("testUnionAllWithNoSelection"),
new UnionCriteriaQueryTest("testIntersectWithNoSelection"),
@@ -72,11 +74,10 @@ public UnionCriteriaQueryTest(String name) {
setPuName(getPersistenceUnitName());
}
- /**
- * The setup is done as a test, both to record its failure, and to allow
- * execution in the server.
- */
- public void testSetup() {
+ // Initialize data
+ @Override
+ protected void suiteSetUp() {
+ super.suiteSetUp();
emf.runInTransaction(em -> {
for (int i = 1; i < POKEMONS.length; i++) {
em.persist(POKEMONS[i]);
@@ -84,22 +85,6 @@ public void testSetup() {
});
}
- private static void verifyValuesOnce(Set expected, List queryResult) {
- Set check = new HashSet<>(expected);
- for (Pokemon pokemon : queryResult) {
- assertTrue(String.format("Pokemon %d:%s was not found in Set %s", pokemon.getId(), pokemon.getName(), expected),
- check.contains(pokemon));
- check.remove(pokemon);
- }
- }
-
- private static void verifyValuesMultiple(Set expected, List queryResult) {
- for (Pokemon pokemon : queryResult) {
- assertTrue(String.format("Pokemon %d:%s was not found in Set %s", pokemon.getId(), pokemon.getName(), expected),
- expected.contains(pokemon));
- }
- }
-
// Test UNION: Shall return distinct values from both queries: 1x Pokemon[1..3]
// Pokemon[1] matches WHERE clause in both selects
public void testUnionWithNoSelection() {
@@ -479,4 +464,20 @@ public void testUnionWithMultiselectEntityInSelection() {
}
}
+ private static void verifyValuesOnce(Set expected, List queryResult) {
+ Set check = new HashSet<>(expected);
+ for (Pokemon pokemon : queryResult) {
+ assertTrue(String.format("Pokemon %d:%s was not found in Set %s", pokemon.getId(), pokemon.getName(), expected),
+ check.contains(pokemon));
+ check.remove(pokemon);
+ }
+ }
+
+ private static void verifyValuesMultiple(Set expected, List queryResult) {
+ for (Pokemon pokemon : queryResult) {
+ assertTrue(String.format("Pokemon %d:%s was not found in Set %s", pokemon.getId(), pokemon.getName(), expected),
+ expected.contains(pokemon));
+ }
+ }
+
}
diff --git a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerFactoryDelegate.java b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerFactoryDelegate.java
index ecf6e031a0f..64a148ea222 100644
--- a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerFactoryDelegate.java
+++ b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerFactoryDelegate.java
@@ -31,6 +31,8 @@
// - 527415: Fix code when locale is tr, az or lt
// 08/23/2023: Tomas Kraus
// - New Jakarta Persistence 3.2 Features
+// 12/05/2023: Tomas Kraus
+// - New Jakarta Persistence 3.2 Features
package org.eclipse.persistence.internal.jpa;
import java.util.Collections;
@@ -159,6 +161,9 @@ public class EntityManagerFactoryDelegate implements EntityManagerFactory, Persi
/** Pointer to the EntityManagerFactoryImpl that created me */
protected JpaEntityManagerFactory owner = null;
+ /** Persistence unit schema manager. */
+ private SchemaManagerImpl schemaManager = null;
+
/**
* Will return an instance of the Factory. Should only be called by
* EclipseLink.
@@ -564,10 +569,12 @@ public PersistenceUnitTransactionType getTransactionType() {
};
}
- // TODO-API-3.2
@Override
public SchemaManager getSchemaManager() {
- throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet");
+ if (schemaManager == null) {
+ schemaManager = new SchemaManagerImpl(getDatabaseSession(), owner.getProperties());
+ }
+ return schemaManager;
}
/**
diff --git a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerSetupImpl.java b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerSetupImpl.java
index b04261220e4..72d4b5577f8 100644
--- a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerSetupImpl.java
+++ b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerSetupImpl.java
@@ -87,7 +87,9 @@
// 12/06/2018 - Will Dazey
// - 542491: Add new 'eclipselink.jdbc.force-bind-parameters' property to force enable binding
// 09/02/2019-3.0 Alexandre Jacob
-// - 527415: Fix code when locale is tr, az or lt
+// - 527415: Fix code when locale is tr, az or lt
+// 12/05/2023: Tomas Kraus
+// - New Jakarta Persistence 3.2 Features
package org.eclipse.persistence.internal.jpa;
import static org.eclipse.persistence.config.PersistenceUnitProperties.DDL_GENERATION;
@@ -110,6 +112,7 @@
import static org.eclipse.persistence.config.PersistenceUnitProperties.SCHEMA_GENERATION_SCRIPT_SOURCE;
import static org.eclipse.persistence.config.PersistenceUnitProperties.SCHEMA_GENERATION_SCRIPT_THEN_METADATA_SOURCE;
import static org.eclipse.persistence.config.PersistenceUnitProperties.SCHEMA_GENERATION_SQL_LOAD_SCRIPT_SOURCE;
+import static org.eclipse.persistence.config.PersistenceUnitProperties.SCHEMA_GENERATION_VERIFY_ACTION;
import static org.eclipse.persistence.internal.jpa.EntityManagerFactoryProvider.generateDefaultTables;
import static org.eclipse.persistence.internal.jpa.EntityManagerFactoryProvider.getConfigProperty;
import static org.eclipse.persistence.internal.jpa.EntityManagerFactoryProvider.getConfigPropertyAsString;
@@ -157,6 +160,7 @@
import jakarta.persistence.OptimisticLockException;
import jakarta.persistence.PersistenceException;
+import jakarta.persistence.SchemaValidationException;
import jakarta.persistence.SharedCacheMode;
import jakarta.persistence.ValidationMode;
import jakarta.persistence.metamodel.Attribute;
@@ -297,6 +301,7 @@
import org.eclipse.persistence.tools.profiler.PerformanceProfiler;
import org.eclipse.persistence.tools.profiler.QueryMonitor;
import org.eclipse.persistence.tools.schemaframework.SchemaManager;
+import org.eclipse.persistence.tools.schemaframework.TableValidationException;
import org.eclipse.persistence.tools.tuning.SafeModeTuner;
import org.eclipse.persistence.tools.tuning.SessionTuner;
import org.eclipse.persistence.tools.tuning.StandardTuner;
@@ -458,10 +463,23 @@ public class EntityManagerSetupImpl implements MetadataRefreshListener {
* will no longer be associated with new EntityManagerFactories
*/
protected boolean isMetadataExpired = false;
- /*
- * Used to distinguish the various DDL options
+
+ /**
+ * Used to distinguish the various DDL options.
+ * Verification option is not included, because it does not modify the database content.
*/
- protected enum TableCreationType {NONE, CREATE, DROP, DROP_AND_CREATE, EXTEND};
+ protected enum TableCreationType {
+ /** Specifies that database tables should not be created or dropped. */
+ NONE,
+ /** Specifies that database tables should be created. */
+ CREATE,
+ /** Specifies that database tables should be dropped. */
+ DROP,
+ /** Specifies that database tables should be dropped, then created. */
+ DROP_AND_CREATE,
+ /** Specifies that database tables should be created and if existing, missing columns will be added. */
+ EXTEND;
+ };
/*
* PersistenceException responsible for the invalid state.
@@ -4485,14 +4503,13 @@ public static void throwPersistenceUnitNameAlreadyInUseException(String puName,
* This call will mean any users of this EntityManagerSetupImpl will get the new version the next time
* they look it up (for instance and EntityManager creation time)
*/
- public EntityManagerSetupImpl refreshMetadata(Map properties){
+ public EntityManagerSetupImpl refreshMetadata(Map properties){
String sessionName = getSessionName();
String uniqueName = getPersistenceUnitUniqueName();
EntityManagerSetupImpl newSetupImpl = new EntityManagerSetupImpl(uniqueName, sessionName);
newSetupImpl.setIsInContainerMode(isInContainerMode);
newSetupImpl.enableWeaving = enableWeaving;
- Map