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..cd1623082a1 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
@@ -265,7 +265,9 @@ 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_failed", "Schema validation failed"},
+ { "schema_validation_missing_table", "The {0} table vaw not found in the schema"}
};
/**
* Return the lookup table.
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..cbbcfa7f076 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
@@ -39,8 +39,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;
/**
*
@@ -1126,6 +1128,25 @@ public void replaceDefaultTables(boolean createSequenceTables, boolean createSeq
}
}
+ public void fastReplaceDefaultTables(boolean generateFKConstraints) {
+ boolean shouldLogExceptionStackTrace = getSession().getSessionLog().shouldLogExceptionStackTrace();
+ session.getSessionLog().setShouldLogExceptionStackTrace(false);
+
+ try {
+ TableCreator tableCreator = getDefaultTableCreator(generateFKConstraints);
+ tableCreator.fastReplaceTables(session, this, generateFKConstraints);
+ } catch (DatabaseException exception) {
+ // Ignore error
+ } finally {
+ session.getSessionLog().setShouldLogExceptionStackTrace(shouldLogExceptionStackTrace);
+ }
+ }
+
+ public boolean validateDefaultTables(Consumer> onFailed, boolean generateFKConstraints) {
+ TableCreator tableCreator = getDefaultTableCreator(generateFKConstraints);
+ return tableCreator.validateTables(session, this, onFailed);
+ }
+
public void setSession(DatabaseSessionImpl session) {
this.session = session;
}
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..cbf8cbe31d7 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
@@ -35,6 +35,7 @@
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 +352,24 @@ public void replaceTables(DatabaseSession session, SchemaManager schemaManager,
replaceTablesAndConstraints(schemaManager, session, createSequenceTables, createSequences);
}
+ void fastReplaceTables(DatabaseSession session, SchemaManager schemaManager, boolean generateFKConstraints) {
+ TableCreator tableCreator = schemaManager.getDefaultTableCreator(generateFKConstraints);
+ String sequenceTableName = tableCreator.getSequenceTableName(session);
+ List tables = tableCreator.getTableDefinitions();
+ for (TableDefinition table : tables) {
+ if (!table.getName().equals(sequenceTableName)) {
+ try {
+ // TODO: JPQL DELETE would be better (slightly slower, but DB independent)
+ schemaManager.getSession()
+ .priviledgedExecuteNonSelectingCall(
+ new org.eclipse.persistence.queries.SQLCall("DELETE FROM " + table.getFullName()));
+ } catch (DatabaseException ex) {
+ //Ignore database exception. eg. If there is no table to delete, it gives database exception.
+ }
+ }
+ }
+ }
+
protected void replaceTablesAndConstraints(SchemaManager schemaManager, DatabaseSession session, boolean createSequenceTables, boolean createSequences) {
buildConstraints(schemaManager, true);
boolean ignore = shouldIgnoreDatabaseException();
@@ -442,6 +461,46 @@ 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 {@link Consumer} to accept {@link List} of {@link TableValidationException}
+ * containing validation failures. Consumer is called only when validation failed.
+ * @return Value of {@code true} when validation passed or {@code false} otherwise.
+ */
+ public boolean validateTables(final DatabaseSession session,
+ final SchemaManager schemaManager,
+ Consumer> onFailed) {
+ //final String sequenceTableName = getSequenceTableName(session);
+ 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)) {
+ // TODO: Check table content
+ List columnInfo = readColumnInfo((AbstractSession) session, tableDefinition);
+ if (columnInfo != null && !columnInfo.isEmpty()) {
+
+ }
+ } else {
+ exceptions.add(new TableValidationException.Missing(tableName));
+ }
+ });
+ if (exceptions.isEmpty()) {
+ return true;
+ } else {
+ // Pass validation failures to provided consumer
+ onFailed.accept(exceptions);
+ return false;
+ }
+ }
+
/**
* This creates/extends the tables on the database.
* @param session Active database session.
@@ -477,20 +536,9 @@ public void extendTables(final DatabaseSession session, final SchemaManager sche
//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
@@ -575,4 +623,22 @@ 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;
+ }
+
}
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..7302805f50b
--- /dev/null
+++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/tools/schemaframework/TableValidationException.java
@@ -0,0 +1,40 @@
+package org.eclipse.persistence.tools.schemaframework;
+
+import jakarta.persistence.SchemaValidationException;
+import org.eclipse.persistence.internal.localization.ExceptionLocalization;
+
+public abstract class TableValidationException extends Exception {
+ private final String table;
+ private final Type type;
+
+ private TableValidationException(String message, String table, Type type) {
+ super(message);
+ this.table = table;
+ this.type = type;
+ }
+
+ public String getTable() {
+ return table;
+ }
+
+ public Type getType() {
+ return type;
+ }
+
+ public static final class Missing extends TableValidationException {
+
+ Missing(String table) {
+ super(ExceptionLocalization.buildMessage(
+ "schema_validation_missing_table", new String[] {table}),
+ table, Type.MISSING);
+ }
+
+ }
+
+ public enum Type {
+ /** Missing table in the schema. */
+ MISSING;
+ }
+
+
+}
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..97706c02bb0 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
@@ -87,6 +87,8 @@ static TestSuite suite(String name, AbstractPokemon... tests) {
return suite;
}
+ private Persistence32TableCreator tableCreator = null;
+
JpaEntityManagerFactory emf = null;
public AbstractPokemon() {
@@ -105,6 +107,7 @@ public String getPersistenceUnitName() {
@Override
public void setUp() {
super.setUp();
+ tableCreator = new Persistence32TableCreator();
emf = getEntityManagerFactory(getPersistenceUnitName())
.unwrap(EntityManagerFactoryImpl.class);
}
@@ -128,7 +131,7 @@ Map pokemonTypes(EntityManager em) {
* execution in the server.
*/
public void testSetup() {
- new Persistence32TableCreator().replaceTables(JUnitTestCase.getServerSession(getPersistenceUnitName()));
+ tableCreator.replaceTables(JUnitTestCase.getServerSession(getPersistenceUnitName()));
emf.runInTransaction(em -> {
for (int i = 1; i < TEAMS.length; i++) {
em.persist(TEAMS[i]);
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..38b46d70b3b
--- /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,107 @@
+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.jpa.junit.JUnitTestCase;
+
+/**
+ * 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.
+ * 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
+ */
+ static TestSuite suite(String name, AbstractSchemaManager... tests) {
+ TestSuite suite = new TestSuite();
+ suite.setName(name);
+ for (AbstractSchemaManager test : tests) {
+ suite.addTest(test);
+ }
+ return suite;
+ }
+
+ 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/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..68563af4326
--- /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,61 @@
+/*
+ * 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;
+
+/**
+ * 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..f36e271d7fd
--- /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,106 @@
+/*
+ * 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;
+
+/**
+ * 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) {
+ assertTrue(
+ "Unexpected exception message: " + pe.getLocalizedMessage(),
+ pe.getLocalizedMessage().contains("does not exist"));
+ }
+ // - 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) {
+ assertTrue(
+ "Unexpected exception message: " + pe.getLocalizedMessage(),
+ pe.getLocalizedMessage().contains("does not exist"));
+ }
+ // - 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) {
+ assertTrue(
+ "Unexpected exception message: " + pe.getLocalizedMessage(),
+ pe.getLocalizedMessage().contains("does not exist"));
+ }
+ 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..e16144e13e9
--- /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,73 @@
+/*
+ * 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;
+
+/**
+ * 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/SchemaManagerValidateOnMissingTest.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/SchemaManagerValidateOnMissingTest.java
new file mode 100644
index 00000000000..30ef4af8a6e
--- /dev/null
+++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/SchemaManagerValidateOnMissingTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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;
+
+/**
+ * Test {@link SchemaManager#validate()} method on database with missing schema.
+ */
+public class SchemaManagerValidateOnMissingTest extends AbstractSchemaManager {
+
+ public static Test suite() {
+ return suite(
+ "SchemaManagerValidateOnMissingTest",
+ new SchemaManagerValidateOnMissingTest("testValidateOnMissingSchema")
+ );
+ }
+ public SchemaManagerValidateOnMissingTest() {
+ }
+
+ public SchemaManagerValidateOnMissingTest(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) {
+ 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/SchemaManagerValidateOnValidTest.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/SchemaManagerValidateOnValidTest.java
new file mode 100644
index 00000000000..d7627108597
--- /dev/null
+++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/SchemaManagerValidateOnValidTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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;
+
+/**
+ * Test {@link SchemaManager#validate()} method on database with existing and valid schema.
+ */
+public class SchemaManagerValidateOnValidTest extends AbstractSchemaManager {
+
+ public static Test suite() {
+ return suite(
+ "SchemaManagerValidateOnValidTest",
+ new SchemaManagerValidateOnValidTest("testValidateOnValidSchema")
+ );
+ }
+ public SchemaManagerValidateOnValidTest() {
+ }
+
+ public SchemaManagerValidateOnValidTest(String name) {
+ super(name);
+ }
+
+ // Test SchemaManager validate method on existing valid schema
+ public void testValidateOnValidSchema() {
+ // Make sure that tables exist before being dropped
+ createTables();
+ SchemaManager schemaManager = emf.getSchemaManager();
+ try {
+ // Test validation
+ schemaManager.validate();
+ } catch (SchemaValidationException sve) {
+ fail(sve.getLocalizedMessage());
+ }
+ }
+
+}
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..1b1e986983f 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
@@ -159,6 +159,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 +567,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());
+ }
+ return schemaManager;
}
/**
diff --git a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/SchemaManagerImpl.java b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/SchemaManagerImpl.java
new file mode 100644
index 00000000000..064eef46845
--- /dev/null
+++ b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/SchemaManagerImpl.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 1998, 2023 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2023 IBM Corporation. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0,
+ * or the Eclipse Distribution License v. 1.0 which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
+ */
+
+// Contributors:
+// 11/28/2023: Tomas Kraus
+// - New Jakarta Persistence 3.2 Features
+package org.eclipse.persistence.internal.jpa;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+import jakarta.persistence.SchemaManager;
+import jakarta.persistence.SchemaValidationException;
+import org.eclipse.persistence.exceptions.DatabaseException;
+import org.eclipse.persistence.internal.localization.ExceptionLocalization;
+import org.eclipse.persistence.internal.sessions.AbstractSession;
+import org.eclipse.persistence.internal.sessions.DatabaseSessionImpl;
+import org.eclipse.persistence.tools.schemaframework.TableCreator;
+import org.eclipse.persistence.tools.schemaframework.TableDefinition;
+import org.eclipse.persistence.tools.schemaframework.TableValidationException;
+
+public class SchemaManagerImpl implements SchemaManager {
+
+ private final org.eclipse.persistence.tools.schemaframework.SchemaManager schemaManager;
+
+ SchemaManagerImpl(DatabaseSessionImpl session) {
+ schemaManager = new org.eclipse.persistence.tools.schemaframework.SchemaManager(session);
+ }
+
+ @Override
+ public void create(boolean createSchemas) {
+ if (createSchemas) {
+ schemaManager.createDefaultTables(true);
+ }
+ }
+
+ @Override
+ public void drop(boolean dropSchemas) {
+ if (dropSchemas) {
+ schemaManager.dropDefaultTables();
+ }
+ }
+
+ // TODO-API-3.2
+ @Override
+ public void validate() throws SchemaValidationException {
+ ValidationFailure failures = new ValidationFailure();
+ if (!schemaManager.validateDefaultTables(failures, true)) {
+ throw new SchemaValidationException(
+ ExceptionLocalization.buildMessage("schema_validation_failed"),
+ failures.result().toArray(new TableValidationException[failures.result().size()]));
+ }
+ }
+
+ @Override
+ public void truncate() {
+ schemaManager.fastReplaceDefaultTables(false);
+ }
+
+ private static final class ValidationFailure implements Consumer> {
+
+ private static List validationResult;
+
+ private ValidationFailure() {
+ validationResult = null;
+ }
+
+ @Override
+ public void accept(List failures) {
+ validationResult = failures;
+ }
+
+ private List result() {
+ return validationResult;
+ }
+
+ }
+
+}