Skip to content

Commit

Permalink
jakartaee/persistence#431 - add SchemaManager
Browse files Browse the repository at this point in the history
Initial implementation.

Signed-off-by: Tomáš Kraus <[email protected]>
  • Loading branch information
Tomas-Kraus committed Nov 29, 2023
1 parent 05a15a9 commit c1915c9
Show file tree
Hide file tree
Showing 13 changed files with 710 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
* <p>
Expand Down Expand Up @@ -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<List<TableValidationException>> onFailed, boolean generateFKConstraints) {
TableCreator tableCreator = getDefaultTableCreator(generateFKConstraints);
return tableCreator.validateTables(session, this, onFailed);
}

public void setSession(DatabaseSessionImpl session) {
this.session = session;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;

/**
* <b>Purpose</b>: This class is responsible for creating the tables defined in the project.
Expand Down Expand Up @@ -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<TableDefinition> 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();
Expand Down Expand Up @@ -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 <b>only</b> 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<List<TableValidationException>> onFailed) {
//final String sequenceTableName = getSequenceTableName(session);
List<TableDefinition> tableDefinitions = getTableDefinitions();
List<TableValidationException> 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<AbstractRecord> 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.
Expand Down Expand Up @@ -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<AbstractRecord> 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<AbstractRecord> columnInfo = readColumnInfo(abstractSession, table);

if (columnInfo != null && !columnInfo.isEmpty()) {
//Table exists, add individual fields as necessary

Expand Down Expand Up @@ -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<AbstractRecord> readColumnInfo(AbstractSession session, TableDefinition table) {
String tableName = table.getTable() == null ? table.getName() : table.getTable().getName();
boolean notUsesDelimiting = table.getTable() == null || !table.getTable().shouldUseDelimiters();

List<AbstractRecord> 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;
}

}
Original file line number Diff line number Diff line change
@@ -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;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ static TestSuite suite(String name, AbstractPokemon... tests) {
return suite;
}

private Persistence32TableCreator tableCreator = null;

JpaEntityManagerFactory emf = null;

public AbstractPokemon() {
Expand All @@ -105,6 +107,7 @@ public String getPersistenceUnitName() {
@Override
public void setUp() {
super.setUp();
tableCreator = new Persistence32TableCreator();
emf = getEntityManagerFactory(getPersistenceUnitName())
.unwrap(EntityManagerFactoryImpl.class);
}
Expand All @@ -128,7 +131,7 @@ Map<Integer, Type> 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]);
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> initialMissingTablesSet, Set<String> missingTablesSet, Set<String> 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()));
}
}
}

}
Loading

0 comments on commit c1915c9

Please sign in to comment.