From b64de528c1a577d79a48eea465578667b0689c60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20B=C3=BChlmann?= Date: Sun, 22 Sep 2024 12:37:59 +0200 Subject: [PATCH] Support oracle --- .github/workflows/build_and_test.yml | 4 +- .github/workflows/publish_to_central.yml | 2 +- .gitignore | 1 + README.md | 4 +- build.gradle | 2 +- .../main/java/ch/ergon/adam/core/Adam.java | 10 +- .../core/db/DefaultMigrationStrategy.java | 2 + .../adam/core/db/LoggingSinkWrapper.java | 6 + .../adam/core/db/interfaces/SchemaSink.java | 2 + gradle-plugin/build.gradle | 10 + .../main/resources/adam/oracle/git_history | 5 + .../adam/oracle/schema/test.table.yml | 35 +++ .../PREMIGRATION_ONCE/2_faulty_script.sql | 1 + .../PREMIGRATION_ONCE/3_first_script.sql | 10 + .../PREMIGRATION_ONCE/4_second_script.sql | 3 + .../PREMIGRATION_ONCE/5_create_table.sql | 5 + .../main/resources/adam/oracle/target_version | 1 + .../PREMIGRATION_ONCE/3_first_script.sql | 4 +- .../PREMIGRATION_ONCE/4_second_script.sql | 1 + .../PREMIGRATION_ONCE/5_create_table.sql | 3 +- .../src/main/resources/adam/target_version | 2 +- integration-test/build.gradle | 21 ++ ...=> AbstractDbMigrationWithSchemaTest.java} | 20 +- ...AbstractDbMigrationWithoutSchemaTest.java} | 18 +- .../integrationtest/AbstractDbTestBase.java | 2 +- .../DbMigrationWithSqliteInMemoryTest.java | 4 +- .../ergon/adam/integrationtest/DummySink.java | 5 + .../AbstractOracleSchemaCoverageTest.java | 92 ++++++ .../oracle/AbstractOracleTestBase.java | 11 + .../oracle/OracleAddFieldTests.java | 9 + .../oracle/OracleCastFieldTypeTests.java | 58 ++++ .../oracle/OracleChangeFieldTypeTest.java | 34 +++ .../oracle/OracleConstraintTests.java | 50 ++++ .../oracle/OracleCreateTableTest.java | 173 ++++++++++++ .../OracleDbMigrationWithSchemaTest.java | 15 + .../OracleDbMigrationWithoutSchemaTest.java | 10 + .../oracle/OracleDefaultTest.java | 34 +++ .../oracle/OracleEmptyDatabaseTest.java | 10 + .../oracle/OracleIndexTests.java | 23 ++ .../oracle/OracleRemoveFieldTests.java | 9 + .../oracle/OracleRenameFieldTests.java | 9 + .../oracle/OracleSchemaCoverageTest.java | 17 ++ .../oracle/OracleTestDbUrlProvider.java | 93 ++++++ .../oracle/OracleViewTests.java | 10 + ...AbstractPostgreSQLSchemaCoverageTest.java} | 4 +- .../postgresql/CastFieldTypeWithEnumTest.java | 2 +- .../postgresql/ConstraintTests.java | 2 +- .../postgresql/EnumArrayFieldTest.java | 4 +- .../integrationtest/postgresql/EnumTests.java | 2 +- .../postgresql/MigrationStepExecutorTest.java | 4 +- .../postgresql/PostgreSqlArrayFieldTest.java | 4 +- .../PostgreSqlCastFieldTypeTests.java | 6 +- .../postgresql/PostgreSqlCreateTableTest.java | 4 +- .../PostgreSqlDbMigrationWithSchemaTest.java | 9 + ...ostgreSqlDbMigrationWithoutSchemaTest.java | 9 + .../PostgreSqlIntervalFieldTest.java | 2 +- .../postgresql/PostgreTimestampSizeTest.java | 4 +- .../PostgresSchemaCoverageTest.java | 2 +- .../postgresql/RollbackOnInsertErrorTest.java | 4 +- .../postgresql/SequenceTests.java | 2 +- .../postgresql/StableSequenceNameTest.java | 2 +- .../postgresql/YmlSchemaCoverageTest.java | 8 +- .../sqlite/SqliteCreateTableTest.java | 5 +- .../sqlite/SqliteIndexTests.java | 7 + .../SqliteTestInMemoryDbUrlProvider.java | 2 +- .../testcases/AddFieldTests.java | 33 +-- .../testcases/CastFieldTypeTest.java | 31 +- .../testcases/ChangeArrayFieldTypeTest.java | 2 +- .../testcases/ChangeFieldTypeTest.java | 43 +-- .../testcases/DefaultTest.java | 29 +- .../testcases/EmptyDatabaseTest.java | 2 +- .../integrationtest/testcases/IndexTests.java | 44 ++- .../testcases/RemoveFieldTests.java | 16 +- .../testcases/RenameFieldTests.java | 16 +- .../testcases/RenameTableTests.java | 6 +- .../testcases/SequenceTests.java | 6 +- .../integrationtest/testcases/ViewTests.java | 14 +- jooq/build.gradle | 2 +- .../java/ch/ergon/adam/jooq/JooqSink.java | 13 +- .../java/ch/ergon/adam/jooq/JooqSource.java | 94 ++++-- .../java/ch/ergon/adam/jooq/JooqUtils.java | 28 ++ oracle/build.gradle | 33 +++ .../ergon/adam/oracle/OracleScriptParser.java | 80 ++++++ .../ergon/adam/oracle/OracleSqlExecutor.java | 26 ++ .../ergon/adam/oracle/OracleSqlFactory.java | 69 +++++ .../ch/ergon/adam/oracle/OracleSqlSink.java | 37 +++ .../ch/ergon/adam/oracle/OracleSqlSource.java | 120 ++++++++ .../oracle/OracleSqlTransactionWrapper.java | 267 ++++++++++++++++++ postgresql/build.gradle | 1 + .../adam/postgresql/PostgreSqlSource.java | 85 +----- .../PostgreSqlTransactionWrapper.java | 5 + settings.gradle | 1 + sqlite/build.gradle | 1 + .../ch/ergon/adam/sqlite/SqliteSource.java | 23 +- .../main/java/ch/ergon/adam/yml/YmlSink.java | 5 + 95 files changed, 1767 insertions(+), 293 deletions(-) create mode 100644 integration-test-db/src/main/resources/adam/oracle/git_history create mode 100644 integration-test-db/src/main/resources/adam/oracle/schema/test.table.yml create mode 100644 integration-test-db/src/main/resources/adam/oracle/scripts/PREMIGRATION_ONCE/2_faulty_script.sql create mode 100644 integration-test-db/src/main/resources/adam/oracle/scripts/PREMIGRATION_ONCE/3_first_script.sql create mode 100644 integration-test-db/src/main/resources/adam/oracle/scripts/PREMIGRATION_ONCE/4_second_script.sql create mode 100644 integration-test-db/src/main/resources/adam/oracle/scripts/PREMIGRATION_ONCE/5_create_table.sql create mode 100644 integration-test-db/src/main/resources/adam/oracle/target_version rename integration-test/src/test/java/ch/ergon/adam/integrationtest/{DbMigrationWithSchemaTest.java => AbstractDbMigrationWithSchemaTest.java} (77%) rename integration-test/src/test/java/ch/ergon/adam/integrationtest/{DbMigrationWithoutSchemaTest.java => AbstractDbMigrationWithoutSchemaTest.java} (88%) create mode 100644 integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/AbstractOracleSchemaCoverageTest.java create mode 100644 integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/AbstractOracleTestBase.java create mode 100644 integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleAddFieldTests.java create mode 100644 integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleCastFieldTypeTests.java create mode 100644 integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleChangeFieldTypeTest.java create mode 100644 integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleConstraintTests.java create mode 100644 integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleCreateTableTest.java create mode 100644 integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleDbMigrationWithSchemaTest.java create mode 100644 integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleDbMigrationWithoutSchemaTest.java create mode 100644 integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleDefaultTest.java create mode 100644 integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleEmptyDatabaseTest.java create mode 100644 integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleIndexTests.java create mode 100644 integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleRemoveFieldTests.java create mode 100644 integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleRenameFieldTests.java create mode 100644 integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleSchemaCoverageTest.java create mode 100644 integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleTestDbUrlProvider.java create mode 100644 integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleViewTests.java rename integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/{AbstractSchemaCoverageTest.java => AbstractPostgreSQLSchemaCoverageTest.java} (97%) create mode 100644 integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgreSqlDbMigrationWithSchemaTest.java create mode 100644 integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgreSqlDbMigrationWithoutSchemaTest.java create mode 100644 jooq/src/main/java/ch/ergon/adam/jooq/JooqUtils.java create mode 100644 oracle/build.gradle create mode 100644 oracle/src/main/java/ch/ergon/adam/oracle/OracleScriptParser.java create mode 100644 oracle/src/main/java/ch/ergon/adam/oracle/OracleSqlExecutor.java create mode 100644 oracle/src/main/java/ch/ergon/adam/oracle/OracleSqlFactory.java create mode 100644 oracle/src/main/java/ch/ergon/adam/oracle/OracleSqlSink.java create mode 100644 oracle/src/main/java/ch/ergon/adam/oracle/OracleSqlSource.java create mode 100644 oracle/src/main/java/ch/ergon/adam/oracle/OracleSqlTransactionWrapper.java diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 69a265a..49790c6 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -14,9 +14,9 @@ jobs: with: fetch-depth: 0 - name: Build - run: ./gradlew -S -i assemble + run: ./gradlew -S -i assemble -PjooqProUser=${{ secrets.JOOQ_PRO_USER }} -PjooqProPassword=${{ secrets.JOOQ_PRO_PASSWORD }} - name: Test - run: ./gradlew -S -i test + run: ./gradlew -S -i test -PjooqProUser=${{ secrets.JOOQ_PRO_USER }} -PjooqProPassword=${{ secrets.JOOQ_PRO_PASSWORD }} - name: Publish Unit Test Results uses: EnricoMi/publish-unit-test-result-action@v2 if: always() diff --git a/.github/workflows/publish_to_central.yml b/.github/workflows/publish_to_central.yml index cbab6e1..fa62b4f 100644 --- a/.github/workflows/publish_to_central.yml +++ b/.github/workflows/publish_to_central.yml @@ -24,7 +24,7 @@ jobs: run: mkdir build && echo '${{secrets.SIGNING_KEY_FILE_BASE64}}' | base64 -d > build/adam_signing_key.gpg - name: Upload - run: ./gradlew -S -i publishReleasePublicationToSonatypeRepository -Psigning.keyId=${{ secrets.SIGNING_KEY_ID }} -Psigning.password='${{ secrets.SIGNING_PASSWORD }}' -Psigning.secretKeyRingFile=../build/adam_signing_key.gpg -PossrhUsername='${{ secrets.OSSRH_USERNAME }}' -PossrhPassword='${{ secrets.OSSRH_PASSWORD }}' -PossrhStagingProfileId='${{ secrets.OSSRH_STAGING_PROFILE_ID }}' + run: ./gradlew -S -i publishReleasePublicationToSonatypeRepository -PjooqProUser=${{ secrets.JOOQ_PRO_USER }} -PjooqProPassword=${{ secrets.JOOQ_PRO_PASSWORD }} -Psigning.keyId=${{ secrets.SIGNING_KEY_ID }} -Psigning.password='${{ secrets.SIGNING_PASSWORD }}' -Psigning.secretKeyRingFile=../build/adam_signing_key.gpg -PossrhUsername='${{ secrets.OSSRH_USERNAME }}' -PossrhPassword='${{ secrets.OSSRH_PASSWORD }}' -PossrhStagingProfileId='${{ secrets.OSSRH_STAGING_PROFILE_ID }}' - name: Cleanup if: always() diff --git a/.gitignore b/.gitignore index 7758d48..8b61f5b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ build/ /integration-test-db/src/main/resources/adamd/target_version gradle-plugin-test/.gradle/ gradle.properties +local.properties diff --git a/README.md b/README.md index 4ee1266..d637c10 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Supported: - YML-Files - PostgreSQL - SQLite +- Oracle ### Schema sink @@ -31,6 +32,7 @@ Supported: - YML-Files - PostgreSQL - SQLite +- Oracle ### Automated schema migrator @@ -69,7 +71,7 @@ In case of the **Once**-scripts the script executor needs to decide which script artificial version of the software. On every migration, the current version is stored in a separate table `db_schema_version` on the database itself. To check which scripts need to be executed, the script executor checks which scripts have been added since the current version of the database. The order of execution is then given by the artificial version. - +za --- **NOTE** diff --git a/build.gradle b/build.gradle index 9c4b614..a87f10f 100644 --- a/build.gradle +++ b/build.gradle @@ -45,7 +45,7 @@ ext.getLocalProperty = { key, file = "local.properties" -> def properties = new Properties() def localProperties = new File(rootProject.rootDir, file) if (localProperties.isFile()) { - new InputStreamReader(new FileInputStream(localProperties), UTF_8).with {properties.load} + new InputStreamReader(new FileInputStream(localProperties), UTF_8).with {properties.load(it)} return properties.getProperty(key, "") } else { return null diff --git a/core/src/main/java/ch/ergon/adam/core/Adam.java b/core/src/main/java/ch/ergon/adam/core/Adam.java index e60a3b4..71aaefd 100644 --- a/core/src/main/java/ch/ergon/adam/core/Adam.java +++ b/core/src/main/java/ch/ergon/adam/core/Adam.java @@ -219,23 +219,23 @@ private void ensureSchemaVersionTable(String targetUrl) { } private void ensureNoInProgressMigrations(SqlExecutor sqlExecutor) { - Object result = sqlExecutor.queryResult(format("SELECT COUNT(1) FROM %s WHERE execution_completed_at IS NULL", SCHEMA_VERSION_TABLE_NAME)); - if (!(result.equals(0L) || result.equals(0))) { + Object result = sqlExecutor.queryResult(format("SELECT COUNT(1) FROM \"%s\" WHERE \"execution_completed_at\" IS NULL", SCHEMA_VERSION_TABLE_NAME)); + if (Integer.parseInt(result.toString()) != 0) { throw new RuntimeException("There is an unfinished migration in [" + SCHEMA_VERSION_TABLE_NAME + "]"); } } private String getDbSchemaVersion(SqlExecutor sqlExecutor) { - Object result = sqlExecutor.queryResult(format("SELECT target_version FROM %s ORDER BY execution_started_at DESC", SCHEMA_VERSION_TABLE_NAME)); + Object result = sqlExecutor.queryResult(format("SELECT \"target_version\" FROM \"%s\" ORDER BY \"execution_started_at\" DESC", SCHEMA_VERSION_TABLE_NAME)); return result == null ? null : result.toString(); } private void createSchemaVersionEntry(SqlExecutor sqlExecutor, String fromVersion, String toVersion) { - sqlExecutor.queryResult(format("INSERT INTO %s (execution_started_at, source_version, target_version) VALUES (CURRENT_TIMESTAMP, ?, ?)", SCHEMA_VERSION_TABLE_NAME), fromVersion, toVersion); + sqlExecutor.queryResult(format("INSERT INTO \"%s\" (\"execution_started_at\", \"source_version\", \"target_version\") VALUES (CURRENT_TIMESTAMP, ?, ?)", SCHEMA_VERSION_TABLE_NAME), fromVersion, toVersion); } private void completeSchemaVersionEntry(SqlExecutor sqlExecutor) { - sqlExecutor.queryResult(format("UPDATE %s SET execution_completed_at = CURRENT_TIMESTAMP WHERE execution_completed_at IS NULL", SCHEMA_VERSION_TABLE_NAME)); + sqlExecutor.queryResult(format("UPDATE \"%s\" SET \"execution_completed_at\" = CURRENT_TIMESTAMP WHERE \"execution_completed_at\" IS NULL", SCHEMA_VERSION_TABLE_NAME)); } public void setAllowUnknownDBVersion(boolean allowUnknownDBVersion) { diff --git a/core/src/main/java/ch/ergon/adam/core/db/DefaultMigrationStrategy.java b/core/src/main/java/ch/ergon/adam/core/db/DefaultMigrationStrategy.java index 31f4f89..753411c 100644 --- a/core/src/main/java/ch/ergon/adam/core/db/DefaultMigrationStrategy.java +++ b/core/src/main/java/ch/ergon/adam/core/db/DefaultMigrationStrategy.java @@ -402,6 +402,8 @@ private void applyTableRecreate(Table sourceTable, Table targetTable, SchemaSink sink.copyData(sourceTable, targetTable, insertSourceTableName); + sink.adjustSequences(targetTable); + sink.dropTable(new Table(insertSourceTableName)); } } diff --git a/core/src/main/java/ch/ergon/adam/core/db/LoggingSinkWrapper.java b/core/src/main/java/ch/ergon/adam/core/db/LoggingSinkWrapper.java index 2ed80b1..16a38be 100644 --- a/core/src/main/java/ch/ergon/adam/core/db/LoggingSinkWrapper.java +++ b/core/src/main/java/ch/ergon/adam/core/db/LoggingSinkWrapper.java @@ -164,6 +164,12 @@ public void dropSequencesAndDefaults(Table table) { wrappedSink.dropSequencesAndDefaults(table); } + @Override + public void adjustSequences(Table table) { + logger.info("Adjust sequences for table [{}]", table.getName()); + wrappedSink.adjustSequences(table); + } + @Override public void close() throws Exception { wrappedSink.close(); diff --git a/core/src/main/java/ch/ergon/adam/core/db/interfaces/SchemaSink.java b/core/src/main/java/ch/ergon/adam/core/db/interfaces/SchemaSink.java index f99d04e..efb3408 100644 --- a/core/src/main/java/ch/ergon/adam/core/db/interfaces/SchemaSink.java +++ b/core/src/main/java/ch/ergon/adam/core/db/interfaces/SchemaSink.java @@ -57,4 +57,6 @@ public interface SchemaSink extends AutoCloseable { default boolean supportAlterAndDropField() { return true; } + + void adjustSequences(Table table); } diff --git a/gradle-plugin/build.gradle b/gradle-plugin/build.gradle index 3c32219..e2cc025 100644 --- a/gradle-plugin/build.gradle +++ b/gradle-plugin/build.gradle @@ -34,3 +34,13 @@ gradlePlugin { } } } + +publishing { + repositories { + maven { + name = "localPluginRepository" + url = uri("/tmp/adam/local-gradle-plugin-repository") + } + } +} + diff --git a/integration-test-db/src/main/resources/adam/oracle/git_history b/integration-test-db/src/main/resources/adam/oracle/git_history new file mode 100644 index 0000000..97d4643 --- /dev/null +++ b/integration-test-db/src/main/resources/adam/oracle/git_history @@ -0,0 +1,5 @@ +1 +2 1 +3 1 +4 2 3 +5 4 diff --git a/integration-test-db/src/main/resources/adam/oracle/schema/test.table.yml b/integration-test-db/src/main/resources/adam/oracle/schema/test.table.yml new file mode 100644 index 0000000..af8b84e --- /dev/null +++ b/integration-test-db/src/main/resources/adam/oracle/schema/test.table.yml @@ -0,0 +1,35 @@ +--- +name: "test_table" +fields: + - name: "id" + dataType: "BIGINT" + sequence: true + - name: "col1" + dataType: "DECIMAL_INTEGER" + - name: "col2" + dataType: "NUMERIC" + defaultValue: "10" + nullable: true + precision: 10 + scale: 2 + - name: "col3" + dataType: "VARCHAR" + nullable: true + - name: "col4" + dataType: "VARCHAR" + length: 10 + - name: "col5" + dataType: "VARCHAR" + nullable: true +foreignKeys: [] +indexes: + - name: "test_table_col1_key" + fields: + - "col1" + unique: true + - name: "test_table_pkey" + fields: + - "id" + primary: true + unique: true +ruleConstraints: [] diff --git a/integration-test-db/src/main/resources/adam/oracle/scripts/PREMIGRATION_ONCE/2_faulty_script.sql b/integration-test-db/src/main/resources/adam/oracle/scripts/PREMIGRATION_ONCE/2_faulty_script.sql new file mode 100644 index 0000000..a8ace16 --- /dev/null +++ b/integration-test-db/src/main/resources/adam/oracle/scripts/PREMIGRATION_ONCE/2_faulty_script.sql @@ -0,0 +1 @@ +This is a faulty script and should cause an error if executed. diff --git a/integration-test-db/src/main/resources/adam/oracle/scripts/PREMIGRATION_ONCE/3_first_script.sql b/integration-test-db/src/main/resources/adam/oracle/scripts/PREMIGRATION_ONCE/3_first_script.sql new file mode 100644 index 0000000..031ed40 --- /dev/null +++ b/integration-test-db/src/main/resources/adam/oracle/scripts/PREMIGRATION_ONCE/3_first_script.sql @@ -0,0 +1,10 @@ +CREATE TABLE a_table +( + id integer +) +; +CREATE TABLE another_table +( + id integer +) +; diff --git a/integration-test-db/src/main/resources/adam/oracle/scripts/PREMIGRATION_ONCE/4_second_script.sql b/integration-test-db/src/main/resources/adam/oracle/scripts/PREMIGRATION_ONCE/4_second_script.sql new file mode 100644 index 0000000..1863a0d --- /dev/null +++ b/integration-test-db/src/main/resources/adam/oracle/scripts/PREMIGRATION_ONCE/4_second_script.sql @@ -0,0 +1,3 @@ +INSERT INTO another_table +VALUES (2) +; diff --git a/integration-test-db/src/main/resources/adam/oracle/scripts/PREMIGRATION_ONCE/5_create_table.sql b/integration-test-db/src/main/resources/adam/oracle/scripts/PREMIGRATION_ONCE/5_create_table.sql new file mode 100644 index 0000000..985569b --- /dev/null +++ b/integration-test-db/src/main/resources/adam/oracle/scripts/PREMIGRATION_ONCE/5_create_table.sql @@ -0,0 +1,5 @@ +CREATE TABLE test_table +( + col1 CLOB +) +; diff --git a/integration-test-db/src/main/resources/adam/oracle/target_version b/integration-test-db/src/main/resources/adam/oracle/target_version new file mode 100644 index 0000000..7813681 --- /dev/null +++ b/integration-test-db/src/main/resources/adam/oracle/target_version @@ -0,0 +1 @@ +5 \ No newline at end of file diff --git a/integration-test-db/src/main/resources/adam/scripts/PREMIGRATION_ONCE/3_first_script.sql b/integration-test-db/src/main/resources/adam/scripts/PREMIGRATION_ONCE/3_first_script.sql index 3d7bfe4..031ed40 100644 --- a/integration-test-db/src/main/resources/adam/scripts/PREMIGRATION_ONCE/3_first_script.sql +++ b/integration-test-db/src/main/resources/adam/scripts/PREMIGRATION_ONCE/3_first_script.sql @@ -1,8 +1,10 @@ CREATE TABLE a_table ( id integer -); +) +; CREATE TABLE another_table ( id integer ) +; diff --git a/integration-test-db/src/main/resources/adam/scripts/PREMIGRATION_ONCE/4_second_script.sql b/integration-test-db/src/main/resources/adam/scripts/PREMIGRATION_ONCE/4_second_script.sql index cf46772..1863a0d 100644 --- a/integration-test-db/src/main/resources/adam/scripts/PREMIGRATION_ONCE/4_second_script.sql +++ b/integration-test-db/src/main/resources/adam/scripts/PREMIGRATION_ONCE/4_second_script.sql @@ -1,2 +1,3 @@ INSERT INTO another_table VALUES (2) +; diff --git a/integration-test-db/src/main/resources/adam/scripts/PREMIGRATION_ONCE/5_create_table.sql b/integration-test-db/src/main/resources/adam/scripts/PREMIGRATION_ONCE/5_create_table.sql index 0cff2b3..f131944 100644 --- a/integration-test-db/src/main/resources/adam/scripts/PREMIGRATION_ONCE/5_create_table.sql +++ b/integration-test-db/src/main/resources/adam/scripts/PREMIGRATION_ONCE/5_create_table.sql @@ -1,4 +1,5 @@ CREATE TABLE test_table ( col1 TEXT -); +) +; diff --git a/integration-test-db/src/main/resources/adam/target_version b/integration-test-db/src/main/resources/adam/target_version index bf0d87a..7813681 100644 --- a/integration-test-db/src/main/resources/adam/target_version +++ b/integration-test-db/src/main/resources/adam/target_version @@ -1 +1 @@ -4 \ No newline at end of file +5 \ No newline at end of file diff --git a/integration-test/build.gradle b/integration-test/build.gradle index d17a971..ce99048 100644 --- a/integration-test/build.gradle +++ b/integration-test/build.gradle @@ -7,23 +7,44 @@ group 'ch.ergon.adam' sourceCompatibility = 21 +ext.jooqProUser = hasProperty("jooqProUser") ? property("jooqProUser").toString() : rootProject.getLocalProperty("JOOQ_PRO_USER") +ext.jooqProPassword = hasProperty("jooqProPassword") ? property("jooqProPassword").toString() : rootProject.getLocalProperty("JOOQ_PRO_PASSWORD") + +repositories { + mavenCentral() + maven { + url = uri("https://repo.jooq.org/repo") + credentials { + username = jooqProUser + password = jooqProPassword + } + } +} + dependencies { testImplementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.14.1' testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.7.1' testImplementation project(':core') testImplementation project(':yml') testImplementation project(':postgresql') + testImplementation project(':oracle') testImplementation project(':sqlite') testImplementation project(':integration-test-db') testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.1' testImplementation group: 'org.hamcrest', name: 'hamcrest-all', version: '1.3' testImplementation("org.testcontainers:testcontainers:1.20.1") + testImplementation("com.fasterxml.jackson.core:jackson-annotations") { + version { + strictly("2.12.2") + } + } testImplementation("org.testcontainers:junit-jupiter:1.20.1") testImplementation("org.testcontainers:postgresql:1.20.1") testImplementation("org.postgresql:postgresql:42.7.1") testImplementation("org.testcontainers:oracle-free:1.20.1") testImplementation('com.oracle.database.jdbc:ojdbc11:23.5.0.24.07') + testImplementation('org.jooq.pro:jooq:3.19.11') } test { diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/DbMigrationWithSchemaTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/AbstractDbMigrationWithSchemaTest.java similarity index 77% rename from integration-test/src/test/java/ch/ergon/adam/integrationtest/DbMigrationWithSchemaTest.java rename to integration-test/src/test/java/ch/ergon/adam/integrationtest/AbstractDbMigrationWithSchemaTest.java index d5c2620..e4f396a 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/DbMigrationWithSchemaTest.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/AbstractDbMigrationWithSchemaTest.java @@ -11,9 +11,7 @@ import java.sql.ResultSet; import java.sql.SQLException; -import static ch.ergon.adam.core.Adam.DEFAULT_ADAM_PACKAGE; -import static ch.ergon.adam.core.Adam.DEFAULT_MAIN_RESOURCE_PATH; -import static ch.ergon.adam.core.Adam.TARGET_VERSION_FILE_NAME; +import static ch.ergon.adam.core.Adam.*; import static ch.ergon.adam.core.prepost.db_schema_version.DbSchemaVersionSource.SCHEMA_VERSION_TABLE_NAME; import static java.lang.String.format; import static java.nio.charset.StandardCharsets.UTF_8; @@ -21,24 +19,32 @@ import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertTrue; -public class DbMigrationWithSchemaTest extends AbstractPostgresqlTestBase { +public abstract class AbstractDbMigrationWithSchemaTest extends AbstractDbTestBase { + + public AbstractDbMigrationWithSchemaTest(TestDbUrlProvider testDbUrlProvider) { + super(testDbUrlProvider); + } private static final String DB_VERSION_5 = "5"; private static final String DB_VERSION_4 = "4"; private Adam getDbMigration(String targetVersion) throws IOException { - Path exportPath = Paths.get("../integration-test-db/" + DEFAULT_MAIN_RESOURCE_PATH + DEFAULT_ADAM_PACKAGE); + Path exportPath = Paths.get("../integration-test-db/" + DEFAULT_MAIN_RESOURCE_PATH + DEFAULT_ADAM_PACKAGE + getExportFolder()); Path targetVersionFile = exportPath.resolve(TARGET_VERSION_FILE_NAME); Files.write(targetVersionFile, targetVersion.getBytes(UTF_8)); return Adam.usingExportDirectory(getTargetDbUrl(), "yml", exportPath.resolve("schema"), exportPath); } + protected String getExportFolder() { + return ""; + } + private void doMigrate(String targetVersion) throws IOException { getDbMigration(targetVersion).execute(); } private String getCurrentSchemaVersion() throws SQLException { - ResultSet result = getTargetDbConnection().createStatement().executeQuery(format("SELECT target_version FROM %s ORDER BY execution_started_at DESC", SCHEMA_VERSION_TABLE_NAME)); + ResultSet result = getTargetDbConnection().createStatement().executeQuery(format("SELECT \"target_version\" FROM \"%s\" ORDER BY \"execution_started_at\" DESC", SCHEMA_VERSION_TABLE_NAME)); assertTrue(result.next()); return result.getString(1); } @@ -47,7 +53,7 @@ private String getCurrentSchemaVersion() throws SQLException { public void testPreMigrationCreatingTable() throws Exception { doMigrate(DB_VERSION_4); assertThat(getCurrentSchemaVersion(), is(DB_VERSION_4)); - getTargetDbConnection().createStatement().execute(format("DROP TABLE test_table")); + getTargetDbConnection().createStatement().execute(format("DROP TABLE \"test_table\"")); doMigrate(DB_VERSION_5); assertThat(getCurrentSchemaVersion(), is(DB_VERSION_5)); } diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/DbMigrationWithoutSchemaTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/AbstractDbMigrationWithoutSchemaTest.java similarity index 88% rename from integration-test/src/test/java/ch/ergon/adam/integrationtest/DbMigrationWithoutSchemaTest.java rename to integration-test/src/test/java/ch/ergon/adam/integrationtest/AbstractDbMigrationWithoutSchemaTest.java index 946d3fb..dd41d9b 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/DbMigrationWithoutSchemaTest.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/AbstractDbMigrationWithoutSchemaTest.java @@ -1,7 +1,6 @@ package ch.ergon.adam.integrationtest; import ch.ergon.adam.core.Adam; -import ch.ergon.adam.integrationtest.postgresql.AbstractPostgresqlTestBase; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -12,9 +11,7 @@ import java.sql.ResultSet; import java.sql.SQLException; -import static ch.ergon.adam.core.Adam.DEFAULT_ADAM_PACKAGE; -import static ch.ergon.adam.core.Adam.DEFAULT_MAIN_RESOURCE_PATH; -import static ch.ergon.adam.core.Adam.TARGET_VERSION_FILE_NAME; +import static ch.ergon.adam.core.Adam.*; import static ch.ergon.adam.core.prepost.db_schema_version.DbSchemaVersionSource.SCHEMA_VERSION_TABLE_NAME; import static java.lang.String.format; import static java.nio.charset.StandardCharsets.UTF_8; @@ -22,7 +19,12 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; -public class DbMigrationWithoutSchemaTest extends AbstractPostgresqlTestBase { +public abstract class AbstractDbMigrationWithoutSchemaTest extends AbstractDbTestBase { + + public AbstractDbMigrationWithoutSchemaTest(TestDbUrlProvider testDbUrlProvider) { + super(testDbUrlProvider); + } + private static final String DB_VERSION_UNKNOWN = "6"; private static final String DB_VERSION_5 = "5"; @@ -42,20 +44,20 @@ private void doMigrate(String targetVersion) throws IOException { } private String getCurrentSchemaVersion() throws SQLException { - ResultSet result = getTargetDbConnection().createStatement().executeQuery(format("SELECT target_version FROM %s ORDER BY execution_started_at DESC", SCHEMA_VERSION_TABLE_NAME)); + ResultSet result = getTargetDbConnection().createStatement().executeQuery(format("SELECT \"target_version\" FROM \"%s\" ORDER BY \"execution_started_at\" DESC", SCHEMA_VERSION_TABLE_NAME)); assertTrue(result.next()); return result.getString(1); } private int countMigrations() throws SQLException { - ResultSet result = getTargetDbConnection().createStatement().executeQuery(format("SELECT count(*) FROM %s", SCHEMA_VERSION_TABLE_NAME)); + ResultSet result = getTargetDbConnection().createStatement().executeQuery(format("SELECT count(*) FROM \"%s\"", SCHEMA_VERSION_TABLE_NAME)); assertTrue(result.next()); return result.getInt(1); } private void setCurrentSchemaVersion(String version) throws SQLException { - getTargetDbConnection().createStatement().execute(format("UPDATE %s SET target_version = '%s'", SCHEMA_VERSION_TABLE_NAME, version)); + getTargetDbConnection().createStatement().execute(format("UPDATE \"%s\" SET \"target_version\" = '%s'", SCHEMA_VERSION_TABLE_NAME, version)); } @Test diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/AbstractDbTestBase.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/AbstractDbTestBase.java index aba8f70..723b9ff 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/AbstractDbTestBase.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/AbstractDbTestBase.java @@ -1,11 +1,11 @@ package ch.ergon.adam.integrationtest; -import ch.ergon.adam.yml.YmlSink; import ch.ergon.adam.core.db.SchemaMigrator; import ch.ergon.adam.core.db.SourceAndSinkFactory; import ch.ergon.adam.core.db.interfaces.SchemaSink; import ch.ergon.adam.core.db.interfaces.SchemaSource; import ch.ergon.adam.core.db.schema.Schema; +import ch.ergon.adam.yml.YmlSink; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/DbMigrationWithSqliteInMemoryTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/DbMigrationWithSqliteInMemoryTest.java index d75d3af..bdde821 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/DbMigrationWithSqliteInMemoryTest.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/DbMigrationWithSqliteInMemoryTest.java @@ -12,9 +12,7 @@ import java.sql.ResultSet; import java.sql.SQLException; -import static ch.ergon.adam.core.Adam.DEFAULT_ADAM_PACKAGE; -import static ch.ergon.adam.core.Adam.DEFAULT_MAIN_RESOURCE_PATH; -import static ch.ergon.adam.core.Adam.TARGET_VERSION_FILE_NAME; +import static ch.ergon.adam.core.Adam.*; import static ch.ergon.adam.core.prepost.db_schema_version.DbSchemaVersionSource.SCHEMA_VERSION_TABLE_NAME; import static java.lang.String.format; import static java.nio.charset.StandardCharsets.UTF_8; diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/DummySink.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/DummySink.java index 03ca0ea..6c278ac 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/DummySink.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/DummySink.java @@ -137,6 +137,11 @@ public void dropSequencesAndDefaults(Table table) { } + @Override + public void adjustSequences(Table table) { + + } + @Override public void close() { diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/AbstractOracleSchemaCoverageTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/AbstractOracleSchemaCoverageTest.java new file mode 100644 index 0000000..762c4d1 --- /dev/null +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/AbstractOracleSchemaCoverageTest.java @@ -0,0 +1,92 @@ +package ch.ergon.adam.integrationtest.oracle; + +import ch.ergon.adam.core.db.SchemaDiffExtractor; +import ch.ergon.adam.core.db.interfaces.SchemaSource; +import ch.ergon.adam.core.db.schema.Schema; +import ch.ergon.adam.core.db.schema.View; +import ch.ergon.adam.core.helper.FileHelper; +import ch.ergon.adam.integrationtest.AssertAnyChangeStrategy; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static ch.ergon.adam.core.helper.CollectorsHelper.createSchemaItemNameArray; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +public abstract class AbstractOracleSchemaCoverageTest extends AbstractOracleTestBase { + + private static Path tempFolder; + + @BeforeAll + public static void setupTempFolder() throws IOException { + tempFolder = Files.createTempDirectory("ADAMTableTest"); + tempFolder.toFile().deleteOnExit(); + } + + @AfterAll + public static void cleanupTempFolder() throws IOException { + FileHelper.deleteFolderRecursively(tempFolder); + } + + private static final String CREATE_TABLE_SQL = + "CREATE TABLE test_table (" + + "id INTEGER GENERATED BY DEFAULT ON NULL AS IDENTITY, " + + "col1 INTEGER NOT NULL, " + + "col2 NUMBER(10,2) DEFAULT 10 NULL, " + + "col3 CLOB NULL, " + + "col4 VARCHAR2(10) NOT NULL, " + + "CONSTRAINT test_table_pkey PRIMARY KEY (id), " + + "CONSTRAINT test_table_col1_key UNIQUE (col1)" + + ")"; + + private static final String CREATE_TABLE_WITH_FK_SQL = + "CREATE TABLE test_fktable (" + + "id INTEGER REFERENCES test_table(id), " + + "CONSTRAINT test_fktable_pkey PRIMARY KEY (id) " + + ")"; + + private static final String CREATE_VIEW = + "CREATE VIEW test_view AS (" + + "SELECT * FROM test_table " + + ")"; + + private static final String CREATE_SEQUENCE = + "CREATE SEQUENCE test_sequence"; + + private static final String CREATE_VIEW2 = + "CREATE VIEW depending_view AS (" + + "SELECT * FROM test_view " + + ")"; + + + @Test + public void testSchemaCoverage() throws Exception { + getSourceDbConnection().createStatement().execute(CREATE_TABLE_SQL); + getSourceDbConnection().createStatement().execute(CREATE_TABLE_WITH_FK_SQL); + getSourceDbConnection().createStatement().execute(CREATE_VIEW); + getSourceDbConnection().createStatement().execute(CREATE_VIEW2); + getSourceDbConnection().createStatement().execute(CREATE_SEQUENCE); + SchemaSource source = getSourceDbSource(); + Schema finalSchema = executeTransformation(source); + + assertSchemaEquals(source.getSchema(), finalSchema); + } + + protected abstract Schema executeTransformation(SchemaSource source) throws Exception; + + private void assertSchemaEquals(Schema sourceSchema, Schema targetSchema) { + SchemaDiffExtractor diffExtractor = new SchemaDiffExtractor(sourceSchema, targetSchema); + diffExtractor.process(new AssertAnyChangeStrategy()); + + sourceSchema.getViews().forEach(sourceView -> { + View targetView = targetSchema.getView(sourceView.getName()); + String[] sourceDependencies = createSchemaItemNameArray(sourceView.getBaseRelations()); + String[] targetDependencies = createSchemaItemNameArray(targetView.getBaseRelations()); + assertArrayEquals(targetDependencies, sourceDependencies); + }); + } +} diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/AbstractOracleTestBase.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/AbstractOracleTestBase.java new file mode 100644 index 0000000..b08da35 --- /dev/null +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/AbstractOracleTestBase.java @@ -0,0 +1,11 @@ +package ch.ergon.adam.integrationtest.oracle; + +import ch.ergon.adam.integrationtest.AbstractDbTestBase; + +public class AbstractOracleTestBase extends AbstractDbTestBase { + + + public AbstractOracleTestBase() { + super(new OracleTestDbUrlProvider()); + } +} diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleAddFieldTests.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleAddFieldTests.java new file mode 100644 index 0000000..88c98e3 --- /dev/null +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleAddFieldTests.java @@ -0,0 +1,9 @@ +package ch.ergon.adam.integrationtest.oracle; + +import ch.ergon.adam.integrationtest.testcases.AddFieldTests; + +public class OracleAddFieldTests extends AddFieldTests { + public OracleAddFieldTests() { + super(new OracleTestDbUrlProvider()); + } +} diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleCastFieldTypeTests.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleCastFieldTypeTests.java new file mode 100644 index 0000000..1569f7b --- /dev/null +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleCastFieldTypeTests.java @@ -0,0 +1,58 @@ +package ch.ergon.adam.integrationtest.oracle; + +import ch.ergon.adam.core.db.schema.Field; +import ch.ergon.adam.core.db.schema.Schema; +import ch.ergon.adam.core.db.schema.Table; +import ch.ergon.adam.integrationtest.DummySink; +import ch.ergon.adam.integrationtest.testcases.CastFieldTypeTest; +import org.junit.jupiter.api.Test; + +import java.sql.ResultSet; + +import static ch.ergon.adam.core.db.schema.DataType.INTEGER; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class OracleCastFieldTypeTests extends CastFieldTypeTest { + public OracleCastFieldTypeTests() { + super(new OracleTestDbUrlProvider()); + } + + @Test + public void testCastVarcharToSerial() throws Exception { + + // Setup db + getTargetDbConnection().createStatement().execute(getCreateTableStatement()); + getTargetDbConnection().createStatement().execute(INSERT_DATA_SQL); + DummySink dummySink = targetToDummy(); + Schema schema = dummySink.getTargetSchema(); + + // Apply change + Table table = schema.getTable("test_table"); + Field field = table.getField("col1"); + field.setDataType(INTEGER); + field.setSequence(true); + migrateTargetWithSchema(schema); + dummySink = targetToDummy(); + schema = dummySink.getTargetSchema(); + + // Verify + table = schema.getTable("test_table"); + assertNotNull(table); + + // Data still present? + ResultSet result = getTargetDbConnection().createStatement().executeQuery("select sum(\"col1\") from \"test_table\""); + assertTrue(result.next()); + assertThat(result.getInt(1), is(2)); + } + + @Override + protected String getCreateTableStatement() { + return "create table \"test_table\" (" + + "\"col1\" varchar2(100), " + + "\"col2\" int " + + ")"; + } +} diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleChangeFieldTypeTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleChangeFieldTypeTest.java new file mode 100644 index 0000000..11cfbca --- /dev/null +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleChangeFieldTypeTest.java @@ -0,0 +1,34 @@ +package ch.ergon.adam.integrationtest.oracle; + +import ch.ergon.adam.integrationtest.testcases.ChangeFieldTypeTest; +import org.junit.jupiter.api.Disabled; + +public class OracleChangeFieldTypeTest extends ChangeFieldTypeTest { + public OracleChangeFieldTypeTest() { + super(new OracleTestDbUrlProvider()); + } + + protected String getCreateTableNotNullSql() { + return "create table \"test_table\" (" + + "\"test_field\" varchar(10) not null " + + ")"; + } + + protected String getCreateTableNullSql() { + return "create table \"test_table\" (" + + "\"test_field\" numeric(10,2) null " + + ")"; + } + + protected String getCreateTableTwoFieldsSql() { + return "create table \"test_table\" (" + + "\"col1\" clob null, " + + "\"col2\" clob null " + + ")"; + } + + @Override + @Disabled + public void changeFromSerialToClob() { + } +} diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleConstraintTests.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleConstraintTests.java new file mode 100644 index 0000000..70f55cb --- /dev/null +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleConstraintTests.java @@ -0,0 +1,50 @@ +package ch.ergon.adam.integrationtest.oracle; + +import ch.ergon.adam.core.db.schema.Schema; +import ch.ergon.adam.integrationtest.DummySink; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertFalse; + +public class OracleConstraintTests extends AbstractOracleTestBase { + + private static final String CREATE_TABLE_SQL = + "create table test_table (" + + "id integer null CHECK (id > 0) " + + ")"; + + @Test + public void testCreateConstraint() throws Exception { + + // Setup db + getSourceDbConnection().createStatement().execute(CREATE_TABLE_SQL); + sourceToTarget(); + DummySink dummySink = targetToDummy(); + + // Verify + Schema schema = dummySink.getTargetSchema(); + assertThat(schema.getTable("TEST_TABLE").getConstraints().size(), is(1)); + } + + @Test + public void testRecreateConstraintAfterTableChange() throws Exception { + + // Setup db + getSourceDbConnection().createStatement().execute(CREATE_TABLE_SQL); + sourceToTarget(); + DummySink dummySink = targetToDummy(); + Schema schema = dummySink.getTargetSchema(); + + // Apply change + schema.getTable("TEST_TABLE").getField("ID").setNullable(false); + migrateTargetWithSchema(schema); + dummySink = targetToDummy(); + schema = dummySink.getTargetSchema(); + + // Verify + assertFalse(schema.getTable("TEST_TABLE").getField("ID").isNullable()); + assertThat(schema.getTable("TEST_TABLE").getConstraints().size(), is(1)); + } +} diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleCreateTableTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleCreateTableTest.java new file mode 100644 index 0000000..de9e84d --- /dev/null +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleCreateTableTest.java @@ -0,0 +1,173 @@ +package ch.ergon.adam.integrationtest.oracle; + +import ch.ergon.adam.core.db.schema.*; +import ch.ergon.adam.integrationtest.AbstractDbTestBase; +import ch.ergon.adam.integrationtest.DummySink; +import org.junit.jupiter.api.Test; + +import static ch.ergon.adam.core.db.schema.DataType.DECIMAL_INTEGER; +import static ch.ergon.adam.core.db.schema.DataType.NUMERIC; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +public class OracleCreateTableTest extends AbstractDbTestBase { + + public OracleCreateTableTest() { + super(new OracleTestDbUrlProvider()); + } + + private static final String YML = "---\n" + + "name: \"TEST_TABLE\"\n" + + "fields:\n" + + "- name: \"ID\"\n" + + " dataType: \"DECIMAL_INTEGER\"\n" + + " precision: 38\n" + + " sequence: true\n" + + "- name: \"COL1\"\n" + + " dataType: \"DECIMAL_INTEGER\"\n" + + " precision: 38\n" + + "- name: \"COL2\"\n" + + " dataType: \"NUMERIC\"\n" + + " defaultValue: \"10\"\n" + + " nullable: true\n" + + " precision: 10\n" + + " scale: 2\n" + + "- name: \"COL3\"\n" + + " dataType: \"CLOB\"\n" + + " nullable: true\n" + + "- name: \"COL4\"\n" + + " dataType: \"VARCHAR\"\n" + + " length: 10\n" + + "indexes:\n" + + "- name: \"TEST_TABLE_COL1_KEY\"\n" + + " fields:\n" + + " - \"COL1\"\n" + + " unique: true\n" + + "- name: \"TEST_TABLE_PKEY\"\n" + + " fields:\n" + + " - \"ID\"\n" + + " primary: true\n" + + " unique: true\n" + + "--- []\n" + + "--- []\n"; + + private static final String CREATE_TABLE_SQL = + "create table test_table (" + + "id INTEGER GENERATED BY DEFAULT ON NULL AS IDENTITY, " + + "col1 INTEGER not null, " + + "col2 NUMBER(10,2) DEFAULT 10 null, " + + "col3 CLOB null, " + + "col4 VARCHAR2(10) not null, " + + "CONSTRAINT test_table_pkey PRIMARY KEY (id), " + + "CONSTRAINT test_table_col1_key UNIQUE (col1)" + + ")"; + + private void verifySchema(Schema schema) { + assertNotNull(schema); + assertThat(schema.getTables().size(), is(1)); + assertTrue(schema.getViews().isEmpty()); + assertTrue(schema.getEnums().isEmpty()); + + Table table = schema.getTable("TEST_TABLE"); + assertNotNull(table); + assertThat(table.getFields().size(), is(5)); + + Field field = table.getField("ID"); + assertNotNull(field); + assertThat(field.getIndex(), is(0)); + assertFalse(field.isNullable()); + assertFalse(field.isArray()); + assertNull(field.getLength()); + assertThat(field.getPrecision(), is(38)); + assertNull(field.getScale()); + assertTrue(field.isSequence()); + assertNull(field.getDefaultValue()); + assertNull(field.getDbEnum()); + assertThat(field.getDataType(), is(DECIMAL_INTEGER)); + + field = table.getField("COL1"); + assertNotNull(field); + assertThat(field.getIndex(), is(1)); + assertFalse(field.isNullable()); + assertFalse(field.isArray()); + assertNull(field.getLength()); + assertThat(field.getPrecision(), is(38)); + assertNull(field.getScale()); + assertFalse(field.isSequence()); + assertNull(field.getDefaultValue()); + assertNull(field.getDbEnum()); + assertThat(field.getDataType(), is(DECIMAL_INTEGER)); + + field = table.getField("COL2"); + assertNotNull(field); + assertThat(field.getIndex(), is(2)); + assertTrue(field.isNullable()); + assertFalse(field.isArray()); + assertNull(field.getLength()); + assertThat(field.getPrecision(), is(10)); + assertThat(field.getScale(), is(2)); + assertFalse(field.isSequence()); + assertThat(field.getDefaultValue(), is("10")); + assertNull(field.getDbEnum()); + assertThat(field.getDataType(), is(NUMERIC)); + + field = table.getField("COL3"); + assertNotNull(field); + assertThat(field.getIndex(), is(3)); + assertTrue(field.isNullable()); + assertFalse(field.isArray()); + assertNull(field.getLength()); + assertNull(field.getPrecision()); + assertNull(field.getScale()); + assertFalse(field.isSequence()); + assertNull(field.getDefaultValue()); + assertNull(field.getDbEnum()); + assertThat(field.getDataType(), is(DataType.CLOB)); + + field = table.getField("COL4"); + assertNotNull(field); + assertThat(field.getIndex(), is(4)); + assertFalse(field.isNullable()); + assertFalse(field.isArray()); + assertThat(field.getLength(), is(10)); + assertNull(field.getPrecision()); + assertNull(field.getScale()); + assertFalse(field.isSequence()); + assertNull(field.getDefaultValue()); + assertNull(field.getDbEnum()); + assertThat(field.getDataType(), is(DataType.VARCHAR)); + + Index index = table.getIndex("TEST_TABLE_PKEY"); + assertNotNull(index); + assertTrue(index.isPrimary()); + assertTrue(index.isUnique()); + assertThat(index.getFields().size(), is(1)); + assertThat(index.getFields().get(0).getName(), is("ID")); + + index = table.getIndex("TEST_TABLE_COL1_KEY"); + assertNotNull(index); + assertFalse(index.isPrimary()); + assertTrue(index.isUnique()); + assertThat(index.getFields().size(), is(1)); + assertThat(index.getFields().get(0).getName(), is("COL1")); + } + + @Test + public void testCreateTable() throws Exception { + getSourceDbConnection().createStatement().execute(CREATE_TABLE_SQL); + sourceToTarget(); + DummySink dummySink = targetToDummy(); + Schema schema = dummySink.getTargetSchema(); + verifySchema(schema); + } + + @Test + public void testCreateTableToYml() throws Exception { + getSourceDbConnection().createStatement().execute(CREATE_TABLE_SQL); + sourceToTarget(); + String yml = targetToYml(); + assertEquals(YML, yml); + } + +} diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleDbMigrationWithSchemaTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleDbMigrationWithSchemaTest.java new file mode 100644 index 0000000..c95b952 --- /dev/null +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleDbMigrationWithSchemaTest.java @@ -0,0 +1,15 @@ +package ch.ergon.adam.integrationtest.oracle; + +import ch.ergon.adam.integrationtest.AbstractDbMigrationWithSchemaTest; +import ch.ergon.adam.integrationtest.postgresql.PostgreSqlTestDbUrlProvider; + +public class OracleDbMigrationWithSchemaTest extends AbstractDbMigrationWithSchemaTest { + public OracleDbMigrationWithSchemaTest() { + super (new OracleTestDbUrlProvider()); + } + + @Override + protected String getExportFolder() { + return "oracle/"; + } +} diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleDbMigrationWithoutSchemaTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleDbMigrationWithoutSchemaTest.java new file mode 100644 index 0000000..a441adf --- /dev/null +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleDbMigrationWithoutSchemaTest.java @@ -0,0 +1,10 @@ +package ch.ergon.adam.integrationtest.oracle; + +import ch.ergon.adam.integrationtest.AbstractDbMigrationWithoutSchemaTest; +import ch.ergon.adam.integrationtest.postgresql.PostgreSqlTestDbUrlProvider; + +public class OracleDbMigrationWithoutSchemaTest extends AbstractDbMigrationWithoutSchemaTest { + public OracleDbMigrationWithoutSchemaTest() { + super (new OracleTestDbUrlProvider()); + } +} diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleDefaultTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleDefaultTest.java new file mode 100644 index 0000000..be1bc3c --- /dev/null +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleDefaultTest.java @@ -0,0 +1,34 @@ +package ch.ergon.adam.integrationtest.oracle; + +import ch.ergon.adam.integrationtest.testcases.DefaultTest; +import org.junit.jupiter.api.Test; + +public class OracleDefaultTest extends DefaultTest { + public OracleDefaultTest() { + super(new OracleTestDbUrlProvider()); + } + + protected String getCreateTableIntDefaultSql() { + return "create table \"test_table\" (" + + "\"id\" number default 1 " + + ")"; + } + + protected String getCreateTableStringDefaultSql() { + return "create table \"test_table\" (" + + "\"id\" VARCHAR(100) default 'defaultValue' " + + ")"; + } + + protected String getCreateTableFunctionDefaultSql() { + return "create table \"test_table\" (" + + "\"id\" varchar(100) default SYS_GUID() " + + ")"; + } + + @Test + @Override + public void testFunctionDefault() throws Exception { + doTestDefault(getCreateTableFunctionDefaultSql(), "SYS_GUID()"); + } +} diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleEmptyDatabaseTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleEmptyDatabaseTest.java new file mode 100644 index 0000000..9464e98 --- /dev/null +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleEmptyDatabaseTest.java @@ -0,0 +1,10 @@ +package ch.ergon.adam.integrationtest.oracle; + +import ch.ergon.adam.integrationtest.testcases.EmptyDatabaseTest; + +public class OracleEmptyDatabaseTest extends EmptyDatabaseTest { + + public OracleEmptyDatabaseTest() { + super(new OracleTestDbUrlProvider()); + } +} diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleIndexTests.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleIndexTests.java new file mode 100644 index 0000000..a5983a0 --- /dev/null +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleIndexTests.java @@ -0,0 +1,23 @@ +package ch.ergon.adam.integrationtest.oracle; + +import ch.ergon.adam.integrationtest.testcases.IndexTests; +import org.junit.jupiter.api.Disabled; + +public class OracleIndexTests extends IndexTests { + public OracleIndexTests() { + super(new OracleTestDbUrlProvider()); + } + + protected String getCreateTableSql() { + return "create table \"test_table\" (" + + "\"id\" integer null unique, " + + "\"col1\" varchar(100)" + + ")"; + } + + @Override + @Disabled + public void testRecreatePartialIndexAfterTableChange() throws Exception { + super.testRecreatePartialIndexAfterTableChange(); + } +} diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleRemoveFieldTests.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleRemoveFieldTests.java new file mode 100644 index 0000000..4dc99ef --- /dev/null +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleRemoveFieldTests.java @@ -0,0 +1,9 @@ +package ch.ergon.adam.integrationtest.oracle; + +import ch.ergon.adam.integrationtest.testcases.RemoveFieldTests; + +public class OracleRemoveFieldTests extends RemoveFieldTests { + public OracleRemoveFieldTests() { + super(new OracleTestDbUrlProvider()); + } +} diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleRenameFieldTests.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleRenameFieldTests.java new file mode 100644 index 0000000..9f87c69 --- /dev/null +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleRenameFieldTests.java @@ -0,0 +1,9 @@ +package ch.ergon.adam.integrationtest.oracle; + +import ch.ergon.adam.integrationtest.testcases.RenameFieldTests; + +public class OracleRenameFieldTests extends RenameFieldTests { + public OracleRenameFieldTests() { + super(new OracleTestDbUrlProvider()); + } +} diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleSchemaCoverageTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleSchemaCoverageTest.java new file mode 100644 index 0000000..f30725a --- /dev/null +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleSchemaCoverageTest.java @@ -0,0 +1,17 @@ +package ch.ergon.adam.integrationtest.oracle; + +import ch.ergon.adam.core.db.SchemaMigrator; +import ch.ergon.adam.core.db.interfaces.SchemaSink; +import ch.ergon.adam.core.db.interfaces.SchemaSource; +import ch.ergon.adam.core.db.schema.Schema; + +public class OracleSchemaCoverageTest extends AbstractOracleSchemaCoverageTest { + + @Override + protected Schema executeTransformation(SchemaSource source) throws Exception { + try (SchemaSink targetSink = getTargetDbSink()) { + new SchemaMigrator(getTargetDbSource(), source, targetSink).migrate(); + } + return getTargetDbSource().getSchema(); + } +} diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleTestDbUrlProvider.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleTestDbUrlProvider.java new file mode 100644 index 0000000..2e92fe4 --- /dev/null +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleTestDbUrlProvider.java @@ -0,0 +1,93 @@ +package ch.ergon.adam.integrationtest.oracle; + +import ch.ergon.adam.integrationtest.TestDbUrlProvider; +import org.jooq.DSLContext; +import org.jooq.impl.DSL; +import org.testcontainers.oracle.OracleContainer; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.time.Duration; + +public class OracleTestDbUrlProvider extends TestDbUrlProvider { + + private static final OracleContainer sourceContainer = new OracleContainer("gvenzl/oracle-free:23.4-slim-faststart"); + private static final OracleContainer targetContainer = new OracleContainer("gvenzl/oracle-free:23.4-slim-faststart"); + + @Override + protected void initDbForTest() throws SQLException { + try (Connection conn = DriverManager.getConnection(getDbUrl(sourceContainer))) { + cleanSchema(conn); + } + try (Connection conn = DriverManager.getConnection(getDbUrl(targetContainer))) { + cleanSchema(conn); + } + } + + private void cleanSchema(Connection conn) throws SQLException { + DSLContext dslContext = DSL.using(conn); + dslContext.execute(""" + BEGIN + FOR cur_rec IN (SELECT object_name, object_type + FROM user_objects + WHERE object_type IN + ('TABLE', + 'VIEW', + 'PACKAGE', + 'PROCEDURE', + 'FUNCTION', + 'SEQUENCE', + 'TYPE', + 'SYNONYM', + 'MATERIALIZED VIEW' + )) + LOOP + BEGIN + IF cur_rec.object_type = 'TABLE' + THEN + EXECUTE IMMEDIATE 'DROP ' + || cur_rec.object_type + || ' "' + || cur_rec.object_name + || '" CASCADE CONSTRAINTS'; + ELSE + EXECUTE IMMEDIATE 'DROP ' + || cur_rec.object_type + || ' "' + || cur_rec.object_name + || '"'; + END IF; + EXCEPTION + WHEN OTHERS + THEN + DBMS_OUTPUT.put_line ( 'FAILED: DROP ' + || cur_rec.object_type + || ' "' + || cur_rec.object_name + || '"' + ); + END; + END LOOP; + END; + """); + } + + @Override + protected String getSourceDbUrl() { + return getDbUrl(sourceContainer); + } + + @Override + protected String getTargetDbUrl() { + return getDbUrl(targetContainer); + } + + protected String getDbUrl(OracleContainer container) { + if (!container.isRunning()) { + container.withStartupTimeout(Duration.ofMinutes(5)); + container.start(); + } + return container.getJdbcUrl().replace("@", container.getUsername() +"/" + container.getPassword() + "@") + "?currentSchema=" + container.getUsername().toUpperCase(); + } +} diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleViewTests.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleViewTests.java new file mode 100644 index 0000000..7d2d6d8 --- /dev/null +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/oracle/OracleViewTests.java @@ -0,0 +1,10 @@ +package ch.ergon.adam.integrationtest.oracle; + +import ch.ergon.adam.integrationtest.postgresql.PostgreSqlTestDbUrlProvider; +import ch.ergon.adam.integrationtest.testcases.ViewTests; + +public class OracleViewTests extends ViewTests { + public OracleViewTests() { + super(new OracleTestDbUrlProvider()); + } +} diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/AbstractSchemaCoverageTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/AbstractPostgreSQLSchemaCoverageTest.java similarity index 97% rename from integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/AbstractSchemaCoverageTest.java rename to integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/AbstractPostgreSQLSchemaCoverageTest.java index a96b652..9be959e 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/AbstractSchemaCoverageTest.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/AbstractPostgreSQLSchemaCoverageTest.java @@ -1,11 +1,11 @@ package ch.ergon.adam.integrationtest.postgresql; -import ch.ergon.adam.integrationtest.AssertAnyChangeStrategy; import ch.ergon.adam.core.db.SchemaDiffExtractor; import ch.ergon.adam.core.db.interfaces.SchemaSource; import ch.ergon.adam.core.db.schema.Schema; import ch.ergon.adam.core.db.schema.View; import ch.ergon.adam.core.helper.FileHelper; +import ch.ergon.adam.integrationtest.AssertAnyChangeStrategy; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -17,7 +17,7 @@ import static ch.ergon.adam.core.helper.CollectorsHelper.createSchemaItemNameArray; import static org.junit.jupiter.api.Assertions.assertArrayEquals; -public abstract class AbstractSchemaCoverageTest extends AbstractPostgresqlTestBase { +public abstract class AbstractPostgreSQLSchemaCoverageTest extends AbstractPostgresqlTestBase { private static Path tempFolder; diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/CastFieldTypeWithEnumTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/CastFieldTypeWithEnumTest.java index 0f66e05..bf89493 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/CastFieldTypeWithEnumTest.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/CastFieldTypeWithEnumTest.java @@ -1,10 +1,10 @@ package ch.ergon.adam.integrationtest.postgresql; -import ch.ergon.adam.integrationtest.DummySink; import ch.ergon.adam.core.db.schema.DbEnum; import ch.ergon.adam.core.db.schema.Field; import ch.ergon.adam.core.db.schema.Schema; import ch.ergon.adam.core.db.schema.Table; +import ch.ergon.adam.integrationtest.DummySink; import org.junit.jupiter.api.Test; import java.sql.ResultSet; diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/ConstraintTests.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/ConstraintTests.java index e89b170..3e7f973 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/ConstraintTests.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/ConstraintTests.java @@ -1,7 +1,7 @@ package ch.ergon.adam.integrationtest.postgresql; -import ch.ergon.adam.integrationtest.DummySink; import ch.ergon.adam.core.db.schema.Schema; +import ch.ergon.adam.integrationtest.DummySink; import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.is; diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/EnumArrayFieldTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/EnumArrayFieldTest.java index d2437f1..e21544d 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/EnumArrayFieldTest.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/EnumArrayFieldTest.java @@ -1,10 +1,10 @@ package ch.ergon.adam.integrationtest.postgresql; -import ch.ergon.adam.integrationtest.AbstractDbTestBase; -import ch.ergon.adam.integrationtest.DummySink; import ch.ergon.adam.core.db.schema.DataType; import ch.ergon.adam.core.db.schema.Field; import ch.ergon.adam.core.db.schema.Schema; +import ch.ergon.adam.integrationtest.AbstractDbTestBase; +import ch.ergon.adam.integrationtest.DummySink; import org.hamcrest.CoreMatchers; import org.junit.jupiter.api.Test; diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/EnumTests.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/EnumTests.java index 2c2ed36..55170a1 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/EnumTests.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/EnumTests.java @@ -1,7 +1,7 @@ package ch.ergon.adam.integrationtest.postgresql; -import ch.ergon.adam.integrationtest.DummySink; import ch.ergon.adam.core.db.schema.Schema; +import ch.ergon.adam.integrationtest.DummySink; import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.is; diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/MigrationStepExecutorTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/MigrationStepExecutorTest.java index 51c49ff..71708cf 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/MigrationStepExecutorTest.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/MigrationStepExecutorTest.java @@ -15,9 +15,7 @@ import java.sql.ResultSet; import java.util.List; -import static ch.ergon.adam.core.Adam.DEFAULT_ADAM_PACKAGE; -import static ch.ergon.adam.core.Adam.DEFAULT_MAIN_RESOURCE_PATH; -import static ch.ergon.adam.core.Adam.HISTORY_FILE_NAME; +import static ch.ergon.adam.core.Adam.*; import static ch.ergon.adam.core.prepost.MigrationStep.PREMIGRATION_ONCE; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgreSqlArrayFieldTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgreSqlArrayFieldTest.java index 936fa3a..a310a4c 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgreSqlArrayFieldTest.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgreSqlArrayFieldTest.java @@ -1,12 +1,10 @@ package ch.ergon.adam.integrationtest.postgresql; +import ch.ergon.adam.core.db.schema.Schema; import ch.ergon.adam.integrationtest.AbstractDbTestBase; import ch.ergon.adam.integrationtest.DummySink; -import ch.ergon.adam.core.db.schema.Schema; import org.junit.jupiter.api.Test; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; public class PostgreSqlArrayFieldTest extends AbstractDbTestBase { diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgreSqlCastFieldTypeTests.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgreSqlCastFieldTypeTests.java index 0b375ef..533444a 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgreSqlCastFieldTypeTests.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgreSqlCastFieldTypeTests.java @@ -1,10 +1,10 @@ package ch.ergon.adam.integrationtest.postgresql; -import ch.ergon.adam.integrationtest.DummySink; -import ch.ergon.adam.integrationtest.testcases.CastFieldTypeTest; import ch.ergon.adam.core.db.schema.Field; import ch.ergon.adam.core.db.schema.Schema; import ch.ergon.adam.core.db.schema.Table; +import ch.ergon.adam.integrationtest.DummySink; +import ch.ergon.adam.integrationtest.testcases.CastFieldTypeTest; import org.junit.jupiter.api.Test; import java.sql.ResultSet; @@ -24,7 +24,7 @@ public PostgreSqlCastFieldTypeTests() { public void testCastVarcharToSerial() throws Exception { // Setup db - getTargetDbConnection().createStatement().execute(CREATE_TABLE_SQL); + getTargetDbConnection().createStatement().execute(getCreateTableStatement()); getTargetDbConnection().createStatement().execute(INSERT_DATA_SQL); DummySink dummySink = targetToDummy(); Schema schema = dummySink.getTargetSchema(); diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgreSqlCreateTableTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgreSqlCreateTableTest.java index b0b8a3a..55214e6 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgreSqlCreateTableTest.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgreSqlCreateTableTest.java @@ -5,9 +5,7 @@ import ch.ergon.adam.integrationtest.DummySink; import org.junit.jupiter.api.Test; -import static ch.ergon.adam.core.db.schema.DataType.BIGINT; -import static ch.ergon.adam.core.db.schema.DataType.DECIMAL_INTEGER; -import static ch.ergon.adam.core.db.schema.DataType.NUMERIC; +import static ch.ergon.adam.core.db.schema.DataType.*; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.*; diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgreSqlDbMigrationWithSchemaTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgreSqlDbMigrationWithSchemaTest.java new file mode 100644 index 0000000..fb4b672 --- /dev/null +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgreSqlDbMigrationWithSchemaTest.java @@ -0,0 +1,9 @@ +package ch.ergon.adam.integrationtest.postgresql; + +import ch.ergon.adam.integrationtest.AbstractDbMigrationWithSchemaTest; + +public class PostgreSqlDbMigrationWithSchemaTest extends AbstractDbMigrationWithSchemaTest { + public PostgreSqlDbMigrationWithSchemaTest() { + super (new PostgreSqlTestDbUrlProvider()); + } +} diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgreSqlDbMigrationWithoutSchemaTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgreSqlDbMigrationWithoutSchemaTest.java new file mode 100644 index 0000000..1e00abe --- /dev/null +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgreSqlDbMigrationWithoutSchemaTest.java @@ -0,0 +1,9 @@ +package ch.ergon.adam.integrationtest.postgresql; + +import ch.ergon.adam.integrationtest.AbstractDbMigrationWithoutSchemaTest; + +public class PostgreSqlDbMigrationWithoutSchemaTest extends AbstractDbMigrationWithoutSchemaTest { + public PostgreSqlDbMigrationWithoutSchemaTest() { + super (new PostgreSqlTestDbUrlProvider()); + } +} diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgreSqlIntervalFieldTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgreSqlIntervalFieldTest.java index 01e145c..dfdb710 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgreSqlIntervalFieldTest.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgreSqlIntervalFieldTest.java @@ -1,8 +1,8 @@ package ch.ergon.adam.integrationtest.postgresql; +import ch.ergon.adam.core.db.schema.Schema; import ch.ergon.adam.integrationtest.AbstractDbTestBase; import ch.ergon.adam.integrationtest.DummySink; -import ch.ergon.adam.core.db.schema.Schema; import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.is; diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgreTimestampSizeTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgreTimestampSizeTest.java index a6c753c..8105576 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgreTimestampSizeTest.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgreTimestampSizeTest.java @@ -1,10 +1,10 @@ package ch.ergon.adam.integrationtest.postgresql; +import ch.ergon.adam.core.db.SchemaDiffExtractor; +import ch.ergon.adam.core.db.interfaces.SchemaSource; import ch.ergon.adam.core.db.schema.*; import ch.ergon.adam.integrationtest.AbstractDbTestBase; import ch.ergon.adam.integrationtest.AssertAnyChangeStrategy; -import ch.ergon.adam.core.db.SchemaDiffExtractor; -import ch.ergon.adam.core.db.interfaces.SchemaSource; import org.junit.jupiter.api.Test; import java.io.IOException; diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgresSchemaCoverageTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgresSchemaCoverageTest.java index b237216..3121393 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgresSchemaCoverageTest.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/PostgresSchemaCoverageTest.java @@ -5,7 +5,7 @@ import ch.ergon.adam.core.db.interfaces.SchemaSource; import ch.ergon.adam.core.db.schema.Schema; -public class PostgresSchemaCoverageTest extends AbstractSchemaCoverageTest { +public class PostgresSchemaCoverageTest extends AbstractPostgreSQLSchemaCoverageTest { @Override protected Schema executeTransformation(SchemaSource source) throws Exception { diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/RollbackOnInsertErrorTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/RollbackOnInsertErrorTest.java index c03a203..7de841e 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/RollbackOnInsertErrorTest.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/RollbackOnInsertErrorTest.java @@ -15,9 +15,7 @@ import java.util.ArrayList; import java.util.List; -import static ch.ergon.adam.core.Adam.DEFAULT_ADAM_PACKAGE; -import static ch.ergon.adam.core.Adam.DEFAULT_MAIN_RESOURCE_PATH; -import static ch.ergon.adam.core.Adam.TARGET_VERSION_FILE_NAME; +import static ch.ergon.adam.core.Adam.*; import static ch.ergon.adam.core.db.schema.DataType.INTEGER; import static ch.ergon.adam.core.prepost.db_schema_version.DbSchemaVersionSource.SCHEMA_VERSION_TABLE_NAME; import static java.nio.charset.StandardCharsets.UTF_8; diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/SequenceTests.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/SequenceTests.java index 52512ef..7c5a601 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/SequenceTests.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/SequenceTests.java @@ -1,8 +1,8 @@ package ch.ergon.adam.integrationtest.postgresql; -import ch.ergon.adam.integrationtest.DummySink; import ch.ergon.adam.core.db.schema.Schema; import ch.ergon.adam.core.db.schema.Sequence; +import ch.ergon.adam.integrationtest.DummySink; import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.is; diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/StableSequenceNameTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/StableSequenceNameTest.java index 013922a..66cc208 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/StableSequenceNameTest.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/StableSequenceNameTest.java @@ -1,9 +1,9 @@ package ch.ergon.adam.integrationtest.postgresql; -import ch.ergon.adam.integrationtest.DummySink; import ch.ergon.adam.core.db.schema.Field; import ch.ergon.adam.core.db.schema.Schema; import ch.ergon.adam.core.db.schema.Table; +import ch.ergon.adam.integrationtest.DummySink; import com.google.common.collect.Lists; import org.junit.jupiter.api.Test; diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/YmlSchemaCoverageTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/YmlSchemaCoverageTest.java index c1f0417..bb07621 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/YmlSchemaCoverageTest.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/postgresql/YmlSchemaCoverageTest.java @@ -1,13 +1,13 @@ package ch.ergon.adam.integrationtest.postgresql; -import ch.ergon.adam.integrationtest.EmptySource; -import ch.ergon.adam.yml.YmlSink; -import ch.ergon.adam.yml.YmlSource; import ch.ergon.adam.core.db.SchemaMigrator; import ch.ergon.adam.core.db.interfaces.SchemaSource; import ch.ergon.adam.core.db.schema.Schema; import ch.ergon.adam.core.filetree.DirectoryTraverser; import ch.ergon.adam.core.helper.FileHelper; +import ch.ergon.adam.integrationtest.EmptySource; +import ch.ergon.adam.yml.YmlSink; +import ch.ergon.adam.yml.YmlSource; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -16,7 +16,7 @@ import java.nio.file.Files; import java.nio.file.Path; -public class YmlSchemaCoverageTest extends AbstractSchemaCoverageTest { +public class YmlSchemaCoverageTest extends AbstractPostgreSQLSchemaCoverageTest { private static Path tempFolder; diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/sqlite/SqliteCreateTableTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/sqlite/SqliteCreateTableTest.java index a7de5d4..fcc5a9b 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/sqlite/SqliteCreateTableTest.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/sqlite/SqliteCreateTableTest.java @@ -10,10 +10,7 @@ import static ch.ergon.adam.core.db.schema.DataType.NUMERIC; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; public abstract class SqliteCreateTableTest extends AbstractDbTestBase { diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/sqlite/SqliteIndexTests.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/sqlite/SqliteIndexTests.java index 62cdc83..94d16c6 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/sqlite/SqliteIndexTests.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/sqlite/SqliteIndexTests.java @@ -1,6 +1,7 @@ package ch.ergon.adam.integrationtest.sqlite; import ch.ergon.adam.integrationtest.testcases.IndexTests; +import org.junit.jupiter.api.Disabled; import java.io.IOException; @@ -8,4 +9,10 @@ public class SqliteIndexTests extends IndexTests { public SqliteIndexTests() throws IOException { super(new SqliteTestFileDbUrlProvider()); } + + @Override + @Disabled + public void testRecreatePartialIndexAfterTableChange() throws Exception { + // https://github.com/jOOQ/jOOQ/issues/16683 + } } diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/sqlite/SqliteTestInMemoryDbUrlProvider.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/sqlite/SqliteTestInMemoryDbUrlProvider.java index 4fde040..d947d84 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/sqlite/SqliteTestInMemoryDbUrlProvider.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/sqlite/SqliteTestInMemoryDbUrlProvider.java @@ -1,8 +1,8 @@ package ch.ergon.adam.integrationtest.sqlite; +import ch.ergon.adam.core.helper.Pair; import ch.ergon.adam.integrationtest.TestDbUrlProvider; import ch.ergon.adam.sqlite.SqliteInMemoryFactory; -import ch.ergon.adam.core.helper.Pair; import java.io.IOException; import java.sql.Connection; diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/AddFieldTests.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/AddFieldTests.java index 5a75b36..7f05d8a 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/AddFieldTests.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/AddFieldTests.java @@ -1,36 +1,31 @@ package ch.ergon.adam.integrationtest.testcases; -import ch.ergon.adam.integrationtest.AbstractDbTestBase; -import ch.ergon.adam.integrationtest.DummySink; -import ch.ergon.adam.integrationtest.TestDbUrlProvider; import ch.ergon.adam.core.db.schema.Field; import ch.ergon.adam.core.db.schema.Schema; import ch.ergon.adam.core.db.schema.Table; +import ch.ergon.adam.integrationtest.AbstractDbTestBase; +import ch.ergon.adam.integrationtest.DummySink; +import ch.ergon.adam.integrationtest.TestDbUrlProvider; import org.junit.jupiter.api.Test; import java.sql.ResultSet; import java.util.ArrayList; import java.util.List; -import static ch.ergon.adam.core.db.schema.DataType.CLOB; -import static ch.ergon.adam.core.db.schema.DataType.INTEGER; -import static ch.ergon.adam.core.db.schema.DataType.VARCHAR; +import static ch.ergon.adam.core.db.schema.DataType.*; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; public abstract class AddFieldTests extends AbstractDbTestBase { private static final String CREATE_TABLE_SQL = - "create table test_table (" + - "id int " + + "create table \"test_table\" (" + + "\"id\" int " + ")"; private static final String INSERT_DATA_SQL = - "insert into test_table values (1)"; + "insert into \"test_table\" values (1)"; public AddFieldTests(TestDbUrlProvider testDbUrlProvider) { super(testDbUrlProvider); @@ -70,7 +65,7 @@ public void testAddFieldAtEndOfTable() throws Exception { assertThat(field.getDefaultValue(), is("'testDefault'")); // Data still present? - ResultSet result = getTargetDbConnection().createStatement().executeQuery("select count(*) from test_table"); + ResultSet result = getTargetDbConnection().createStatement().executeQuery("select count(*) from \"test_table\""); assertTrue(result.next()); assertThat(result.getInt(1), is(1)); @@ -113,7 +108,7 @@ public void testAddFieldAtBeginOfTable() throws Exception { assertThat(field.getDefaultValue(), is("'abcd'")); // Data still present? - ResultSet result = getTargetDbConnection().createStatement().executeQuery("select count(*) from test_table"); + ResultSet result = getTargetDbConnection().createStatement().executeQuery("select count(*) from \"test_table\""); assertTrue(result.next()); assertThat(result.getInt(1), is(1)); } @@ -133,13 +128,13 @@ public void testAddFieldAtEndOfTableWithSimpleMigrationNotNullWithDefault() thro Field newField = new Field("new_field"); newField.setDataType(INTEGER); newField.setDefaultValue("1"); - newField.setSqlForNew("id"); + newField.setSqlForNew("\"id\""); fields.add(newField); table.setFields(fields); migrateTargetWithSchema(schema); // Data still present? - ResultSet result = getTargetDbConnection().createStatement().executeQuery("select sum(new_field) from test_table"); + ResultSet result = getTargetDbConnection().createStatement().executeQuery("select sum(\"new_field\") from \"test_table\""); assertTrue(result.next()); assertThat(result.getInt(1), is(1)); @@ -159,13 +154,13 @@ public void testAddFieldAtEndOfTableWithComplexMigrationNotNullWithoutDefault() List fields = new ArrayList<>(table.getFields()); Field newField = new Field("new_field"); newField.setDataType(INTEGER); - newField.setSqlForNew("id + 1"); + newField.setSqlForNew("\"id\" + 1"); fields.add(newField); table.setFields(fields); migrateTargetWithSchema(schema); // Data still present? - ResultSet result = getTargetDbConnection().createStatement().executeQuery("select sum(new_field) from test_table"); + ResultSet result = getTargetDbConnection().createStatement().executeQuery("select sum(\"new_field\") from \"test_table\""); assertTrue(result.next()); assertThat(result.getInt(1), is(2)); diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/CastFieldTypeTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/CastFieldTypeTest.java index da4ff26..27146ba 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/CastFieldTypeTest.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/CastFieldTypeTest.java @@ -1,11 +1,11 @@ package ch.ergon.adam.integrationtest.testcases; -import ch.ergon.adam.integrationtest.AbstractDbTestBase; -import ch.ergon.adam.integrationtest.DummySink; -import ch.ergon.adam.integrationtest.TestDbUrlProvider; import ch.ergon.adam.core.db.schema.Field; import ch.ergon.adam.core.db.schema.Schema; import ch.ergon.adam.core.db.schema.Table; +import ch.ergon.adam.integrationtest.AbstractDbTestBase; +import ch.ergon.adam.integrationtest.DummySink; +import ch.ergon.adam.integrationtest.TestDbUrlProvider; import org.junit.jupiter.api.Test; import java.sql.ResultSet; @@ -19,15 +19,8 @@ public abstract class CastFieldTypeTest extends AbstractDbTestBase { - - protected static final String CREATE_TABLE_SQL = - "create table test_table (" + - "col1 varchar, " + - "col2 int " + - ")"; - protected static final String INSERT_DATA_SQL = - "insert into test_table values ('2', 2)"; + "insert into \"test_table\" values ('2', 2)"; public CastFieldTypeTest(TestDbUrlProvider testDbUrlProvider) { super(testDbUrlProvider); @@ -37,7 +30,7 @@ public CastFieldTypeTest(TestDbUrlProvider testDbUrlProvider) { public void testCastVarcharToInt() throws Exception { // Setup db - getTargetDbConnection().createStatement().execute(CREATE_TABLE_SQL); + getTargetDbConnection().createStatement().execute(getCreateTableStatement()); getTargetDbConnection().createStatement().execute(INSERT_DATA_SQL); DummySink dummySink = targetToDummy(); Schema schema = dummySink.getTargetSchema(); @@ -55,7 +48,7 @@ public void testCastVarcharToInt() throws Exception { assertNotNull(table); // Data still present? - ResultSet result = getTargetDbConnection().createStatement().executeQuery("select sum(col1) from test_table"); + ResultSet result = getTargetDbConnection().createStatement().executeQuery("select sum(\"col1\") from \"test_table\""); assertTrue(result.next()); assertThat(result.getInt(1), is(2)); } @@ -64,7 +57,7 @@ public void testCastVarcharToInt() throws Exception { public void testCastIntToVarchar() throws Exception { // Setup db - getTargetDbConnection().createStatement().execute(CREATE_TABLE_SQL); + getTargetDbConnection().createStatement().execute(getCreateTableStatement()); getTargetDbConnection().createStatement().execute(INSERT_DATA_SQL); DummySink dummySink = targetToDummy(); Schema schema = dummySink.getTargetSchema(); @@ -82,10 +75,16 @@ public void testCastIntToVarchar() throws Exception { assertNotNull(table); // Data still present? - ResultSet result = getTargetDbConnection().createStatement().executeQuery("select count(col2) from test_table where col2= '2'"); + ResultSet result = getTargetDbConnection().createStatement().executeQuery("select count(\"col2\") from \"test_table\" where \"col2\"= '2'"); assertTrue(result.next()); assertThat(result.getInt(1), is(1)); } - + protected String getCreateTableStatement() { + return + "create table \"test_table\" (" + + "\"col1\" varchar, " + + "\"col2\" int " + + ")"; + } } diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/ChangeArrayFieldTypeTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/ChangeArrayFieldTypeTest.java index 69de27e..c0cfb3a 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/ChangeArrayFieldTypeTest.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/ChangeArrayFieldTypeTest.java @@ -1,9 +1,9 @@ package ch.ergon.adam.integrationtest.testcases; +import ch.ergon.adam.core.db.schema.Schema; import ch.ergon.adam.integrationtest.AbstractDbTestBase; import ch.ergon.adam.integrationtest.DummySink; import ch.ergon.adam.integrationtest.TestDbUrlProvider; -import ch.ergon.adam.core.db.schema.Schema; import org.junit.jupiter.api.Test; import static ch.ergon.adam.core.db.schema.DataType.DECIMAL_INTEGER; diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/ChangeFieldTypeTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/ChangeFieldTypeTest.java index 9b210ec..d956088 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/ChangeFieldTypeTest.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/ChangeFieldTypeTest.java @@ -1,11 +1,11 @@ package ch.ergon.adam.integrationtest.testcases; -import ch.ergon.adam.integrationtest.AbstractDbTestBase; -import ch.ergon.adam.integrationtest.DummySink; -import ch.ergon.adam.integrationtest.TestDbUrlProvider; import ch.ergon.adam.core.db.schema.Field; import ch.ergon.adam.core.db.schema.Schema; import ch.ergon.adam.core.db.schema.Table; +import ch.ergon.adam.integrationtest.AbstractDbTestBase; +import ch.ergon.adam.integrationtest.DummySink; +import ch.ergon.adam.integrationtest.TestDbUrlProvider; import org.junit.jupiter.api.Test; import java.util.ArrayList; @@ -14,33 +14,34 @@ import static ch.ergon.adam.core.db.schema.DataType.CLOB; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; public abstract class ChangeFieldTypeTest extends AbstractDbTestBase { - private static final String CREATE_TABLE_SERIAL_SQL = - "create table test_table (" + + protected String getCreateTableSerialSql() { + return "create table test_table (" + "id bigserial " + ")"; + } - private static final String CREATE_TABLE_NOT_NULL_SQL = - "create table test_table (" + + protected String getCreateTableNotNullSql() { + return "create table test_table (" + "test_field varchar not null " + ")"; + } - private static final String CREATE_TABLE_NULL_SQL = - "create table test_table (" + + protected String getCreateTableNullSql() { + return "create table test_table (" + "test_field numeric(10,2) null " + ")"; + } - private static final String CREATE_TABLE_TWO_FIELDS_SQL = - "create table test_table (" + + protected String getCreateTableTwoFieldsSql() { + return "create table test_table (" + "col1 text null, " + "col2 text null " + ")"; + } public ChangeFieldTypeTest(TestDbUrlProvider testDbUrlProvider) { super(testDbUrlProvider); @@ -50,7 +51,7 @@ public ChangeFieldTypeTest(TestDbUrlProvider testDbUrlProvider) { public void changeFromSerialToClob() throws Exception { // Setup db - getTargetDbConnection().createStatement().execute(CREATE_TABLE_SERIAL_SQL); + getTargetDbConnection().createStatement().execute(getCreateTableSerialSql()); DummySink dummySink = targetToDummy(); Schema schema = dummySink.getTargetSchema(); @@ -84,7 +85,7 @@ public void changeFromSerialToClob() throws Exception { public void changeNotNullToNull() throws Exception { // Setup db - getTargetDbConnection().createStatement().execute(CREATE_TABLE_NOT_NULL_SQL); + getTargetDbConnection().createStatement().execute(getCreateTableNotNullSql()); DummySink dummySink = targetToDummy(); Schema schema = dummySink.getTargetSchema(); @@ -102,7 +103,7 @@ public void changeNotNullToNull() throws Exception { public void changeNullToNotNull() throws Exception { // Setup db - getTargetDbConnection().createStatement().execute(CREATE_TABLE_NULL_SQL); + getTargetDbConnection().createStatement().execute(getCreateTableNullSql()); DummySink dummySink = targetToDummy(); Schema schema = dummySink.getTargetSchema(); @@ -120,7 +121,7 @@ public void changeNullToNotNull() throws Exception { public void changePrecision() throws Exception { // Setup db - getTargetDbConnection().createStatement().execute(CREATE_TABLE_NULL_SQL); + getTargetDbConnection().createStatement().execute(getCreateTableNullSql()); DummySink dummySink = targetToDummy(); Schema schema = dummySink.getTargetSchema(); @@ -138,7 +139,7 @@ public void changePrecision() throws Exception { public void changeScale() throws Exception { // Setup db - getTargetDbConnection().createStatement().execute(CREATE_TABLE_NULL_SQL); + getTargetDbConnection().createStatement().execute(getCreateTableNullSql()); DummySink dummySink = targetToDummy(); Schema schema = dummySink.getTargetSchema(); @@ -156,7 +157,7 @@ public void changeScale() throws Exception { public void changeFieldOrder() throws Exception { // Setup db - getTargetDbConnection().createStatement().execute(CREATE_TABLE_TWO_FIELDS_SQL); + getTargetDbConnection().createStatement().execute(getCreateTableTwoFieldsSql()); DummySink dummySink = targetToDummy(); Schema schema = dummySink.getTargetSchema(); diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/DefaultTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/DefaultTest.java index 92100be..3feeb64 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/DefaultTest.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/DefaultTest.java @@ -1,9 +1,9 @@ package ch.ergon.adam.integrationtest.testcases; +import ch.ergon.adam.core.db.schema.Schema; import ch.ergon.adam.integrationtest.AbstractDbTestBase; import ch.ergon.adam.integrationtest.DummySink; import ch.ergon.adam.integrationtest.TestDbUrlProvider; -import ch.ergon.adam.core.db.schema.Schema; import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.is; @@ -12,20 +12,23 @@ public abstract class DefaultTest extends AbstractDbTestBase { - private static final String CREATE_TABLE_INT_DEFAULT_SQL = - "create table test_table (" + + protected String getCreateTableIntDefaultSql() { + return "create table test_table (" + "id bigint default 1 " + ")"; + } - private static final String CREATE_TABLE_STRING_DEFAULT_SQL = - "create table test_table (" + + protected String getCreateTableStringDefaultSql() { + return "create table test_table (" + "id VARCHAR default 'defaultValue' " + ")"; + } - private static final String CREATE_TABLE_FUNCTION_DEFAULT_SQL = - "create table test_table (" + + protected String getCreateTableFunctionDefaultSql() { + return "create table test_table (" + "id bigint default char_length('defaultValue') " + ")"; + } public DefaultTest(TestDbUrlProvider testDbUrlProvider) { super(testDbUrlProvider); @@ -33,20 +36,20 @@ public DefaultTest(TestDbUrlProvider testDbUrlProvider) { @Test public void testIntDefault() throws Exception { - doTestDefault(CREATE_TABLE_INT_DEFAULT_SQL, "1"); + doTestDefault(getCreateTableIntDefaultSql(), "1"); } @Test public void testStringDefault() throws Exception { - doTestDefault(CREATE_TABLE_STRING_DEFAULT_SQL, "'defaultValue'"); + doTestDefault(getCreateTableStringDefaultSql(), "'defaultValue'"); } @Test public void testFunctionDefault() throws Exception { - doTestDefault(CREATE_TABLE_FUNCTION_DEFAULT_SQL, "char_length('defaultValue')"); + doTestDefault(getCreateTableFunctionDefaultSql(), "char_length('defaultValue')"); } - private void doTestDefault(String sql, String expectedDefault) throws Exception { + protected void doTestDefault(String sql, String expectedDefault) throws Exception { // Setup db getSourceDbConnection().createStatement().execute(sql); @@ -64,7 +67,7 @@ private void doTestDefault(String sql, String expectedDefault) throws Exception @Test public void testDropDefault() throws Exception { - getSourceDbConnection().createStatement().execute(CREATE_TABLE_INT_DEFAULT_SQL); + getSourceDbConnection().createStatement().execute(getCreateTableIntDefaultSql()); sourceToTarget(); DummySink dummySink = targetToDummy(); Schema schema = dummySink.getTargetSchema(); @@ -80,7 +83,7 @@ public void testDropDefault() throws Exception { @Test public void testChangeDefault() throws Exception { - getSourceDbConnection().createStatement().execute(CREATE_TABLE_INT_DEFAULT_SQL); + getSourceDbConnection().createStatement().execute(getCreateTableIntDefaultSql()); sourceToTarget(); DummySink dummySink = targetToDummy(); Schema schema = dummySink.getTargetSchema(); diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/EmptyDatabaseTest.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/EmptyDatabaseTest.java index 2ab0bb4..f7e09b7 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/EmptyDatabaseTest.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/EmptyDatabaseTest.java @@ -1,9 +1,9 @@ package ch.ergon.adam.integrationtest.testcases; +import ch.ergon.adam.core.db.schema.Schema; import ch.ergon.adam.integrationtest.AbstractDbTestBase; import ch.ergon.adam.integrationtest.DummySink; import ch.ergon.adam.integrationtest.TestDbUrlProvider; -import ch.ergon.adam.core.db.schema.Schema; import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.is; diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/IndexTests.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/IndexTests.java index a35697c..cfc8824 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/IndexTests.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/IndexTests.java @@ -1,26 +1,27 @@ package ch.ergon.adam.integrationtest.testcases; +import ch.ergon.adam.core.db.schema.Schema; import ch.ergon.adam.integrationtest.AbstractDbTestBase; import ch.ergon.adam.integrationtest.DummySink; import ch.ergon.adam.integrationtest.TestDbUrlProvider; -import ch.ergon.adam.core.db.schema.Schema; import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; public abstract class IndexTests extends AbstractDbTestBase { - private static final String CREATE_TABLE_SQL = - "create table test_table (" + + protected String getCreateTableSql() { + return "create table test_table (" + "id integer null unique, " + "col1 varchar" + ")"; + } - private static final String CREATE_PARTIAL_INDEX_SQL = - "create unique index partial_idx on test_table(id) where col1 = 'test'"; + protected String getCreatePartialIndexSql() { + return "create unique index partial_idx on test_table(id) where col1 = 'test'"; + } public IndexTests(TestDbUrlProvider testDbUrlProvider) { super(testDbUrlProvider); @@ -30,7 +31,7 @@ public IndexTests(TestDbUrlProvider testDbUrlProvider) { public void testCreateIndex() throws Exception { // Setup db - getSourceDbConnection().createStatement().execute(CREATE_TABLE_SQL); + getSourceDbConnection().createStatement().execute(getCreateTableSql()); sourceToTarget(); DummySink dummySink = targetToDummy(); Schema schema = dummySink.getTargetSchema(); @@ -43,8 +44,29 @@ public void testCreateIndex() throws Exception { public void testRecreateIndexAfterTableChange() throws Exception { // Setup db - getSourceDbConnection().createStatement().execute(CREATE_TABLE_SQL); - getSourceDbConnection().createStatement().execute(CREATE_PARTIAL_INDEX_SQL); + getSourceDbConnection().createStatement().execute(getCreateTableSql()); + + sourceToTarget(); + DummySink dummySink = targetToDummy(); + Schema schema = dummySink.getTargetSchema(); + + // Apply change + schema.getTable("test_table").getField("id").setNullable(false); + migrateTargetWithSchema(schema); + dummySink = targetToDummy(); + schema = dummySink.getTargetSchema(); + + // Verify + assertFalse(schema.getTable("test_table").getField("id").isNullable()); + assertThat(schema.getTable("test_table").getIndexes().size(), is(1)); + } + + @Test + public void testRecreatePartialIndexAfterTableChange() throws Exception { + + // Setup db + getSourceDbConnection().createStatement().execute(getCreateTableSql()); + getSourceDbConnection().createStatement().execute(getCreatePartialIndexSql()); sourceToTarget(); DummySink dummySink = targetToDummy(); @@ -59,8 +81,6 @@ public void testRecreateIndexAfterTableChange() throws Exception { // Verify assertFalse(schema.getTable("test_table").getField("id").isNullable()); assertThat(schema.getTable("test_table").getIndexes().size(), is(2)); - if (!this.getClass().getSimpleName().contains("Sqlite")) { // https://github.com/jOOQ/jOOQ/issues/16683 - assertThat(schema.getTable("test_table").getIndex("partial_idx").getWhere(), is("(((col1)::text = 'test'::text))")); - } + assertThat(schema.getTable("test_table").getIndex("partial_idx").getWhere(), is("(((col1)::text = 'test'::text))")); } } diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/RemoveFieldTests.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/RemoveFieldTests.java index f5243de..831fcd3 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/RemoveFieldTests.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/RemoveFieldTests.java @@ -1,10 +1,10 @@ package ch.ergon.adam.integrationtest.testcases; +import ch.ergon.adam.core.db.schema.Schema; +import ch.ergon.adam.core.db.schema.Table; import ch.ergon.adam.integrationtest.AbstractDbTestBase; import ch.ergon.adam.integrationtest.DummySink; import ch.ergon.adam.integrationtest.TestDbUrlProvider; -import ch.ergon.adam.core.db.schema.Schema; -import ch.ergon.adam.core.db.schema.Table; import org.junit.jupiter.api.Test; import java.sql.ResultSet; @@ -19,13 +19,13 @@ public abstract class RemoveFieldTests extends AbstractDbTestBase { private static final String CREATE_TABLE_SQL = - "create table test_table (" + - "col1 int, " + - "col2 int " + + "create table \"test_table\" (" + + "\"col1\" int, " + + "\"col2\" int " + ")"; private static final String INSERT_DATA_SQL = - "insert into test_table values (2, 3)"; + "insert into \"test_table\" values (2, 3)"; public RemoveFieldTests(TestDbUrlProvider testDbUrlProvider) { super(testDbUrlProvider); @@ -53,7 +53,7 @@ public void testSimpleRemoveField() throws Exception { assertThat(table.getFields().size(), is(1)); // Data still present? - ResultSet result = getTargetDbConnection().createStatement().executeQuery("select sum(col1) from test_table"); + ResultSet result = getTargetDbConnection().createStatement().executeQuery("select sum(\"col1\") from \"test_table\""); assertTrue(result.next()); assertThat(result.getInt(1), is(2)); } @@ -83,7 +83,7 @@ public void testSimpleRemoveFieldWithTableRename() throws Exception { assertThat(newTable.getFields().size(), is(1)); // Data still present? - ResultSet result = getTargetDbConnection().createStatement().executeQuery("select sum(col1) from new_table"); + ResultSet result = getTargetDbConnection().createStatement().executeQuery("select sum(\"col1\") from \"new_table\""); assertTrue(result.next()); assertThat(result.getInt(1), is(2)); } diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/RenameFieldTests.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/RenameFieldTests.java index efad966..c268886 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/RenameFieldTests.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/RenameFieldTests.java @@ -1,11 +1,11 @@ package ch.ergon.adam.integrationtest.testcases; -import ch.ergon.adam.integrationtest.AbstractDbTestBase; -import ch.ergon.adam.integrationtest.DummySink; -import ch.ergon.adam.integrationtest.TestDbUrlProvider; import ch.ergon.adam.core.db.schema.Field; import ch.ergon.adam.core.db.schema.Schema; import ch.ergon.adam.core.db.schema.Table; +import ch.ergon.adam.integrationtest.AbstractDbTestBase; +import ch.ergon.adam.integrationtest.DummySink; +import ch.ergon.adam.integrationtest.TestDbUrlProvider; import com.google.common.collect.Lists; import org.junit.jupiter.api.Test; @@ -20,12 +20,12 @@ public abstract class RenameFieldTests extends AbstractDbTestBase { private static final String CREATE_TABLE_SQL = - "create table test_table (" + - "col1 int " + + "create table \"test_table\" (" + + "\"col1\" int " + ")"; private static final String INSERT_DATA_SQL = - "insert into test_table values (2)"; + "insert into \"test_table\" values (2)"; public RenameFieldTests(TestDbUrlProvider testDbUrlProvider) { super(testDbUrlProvider); @@ -43,7 +43,7 @@ public void testSimpleRenameField() throws Exception { // Apply change Table table = schema.getTable("test_table"); Field newColField = new Field("new_col"); - newColField.setSqlForNew("col1"); + newColField.setSqlForNew("\"col1\""); newColField.setDataType(INTEGER); newColField.setNullable(true); table.setFields(Lists.newArrayList(newColField)); @@ -59,7 +59,7 @@ public void testSimpleRenameField() throws Exception { assertNotNull(newColField); // Data still present? - ResultSet result = getTargetDbConnection().createStatement().executeQuery("select sum(new_col) from test_table"); + ResultSet result = getTargetDbConnection().createStatement().executeQuery("select sum(\"new_col\") from \"test_table\""); assertTrue(result.next()); assertThat(result.getInt(1), is(2)); } diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/RenameTableTests.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/RenameTableTests.java index 46f9236..4f5d62f 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/RenameTableTests.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/RenameTableTests.java @@ -1,11 +1,11 @@ package ch.ergon.adam.integrationtest.testcases; -import ch.ergon.adam.integrationtest.AbstractDbTestBase; -import ch.ergon.adam.integrationtest.DummySink; -import ch.ergon.adam.integrationtest.TestDbUrlProvider; import ch.ergon.adam.core.db.schema.Field; import ch.ergon.adam.core.db.schema.Schema; import ch.ergon.adam.core.db.schema.Table; +import ch.ergon.adam.integrationtest.AbstractDbTestBase; +import ch.ergon.adam.integrationtest.DummySink; +import ch.ergon.adam.integrationtest.TestDbUrlProvider; import org.junit.jupiter.api.Test; import java.sql.ResultSet; diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/SequenceTests.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/SequenceTests.java index b7360e1..2983e02 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/SequenceTests.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/SequenceTests.java @@ -1,12 +1,12 @@ package ch.ergon.adam.integrationtest.testcases; +import ch.ergon.adam.core.db.SchemaDiffExtractor; +import ch.ergon.adam.core.db.schema.Schema; +import ch.ergon.adam.core.db.schema.Sequence; import ch.ergon.adam.integrationtest.AbstractDbTestBase; import ch.ergon.adam.integrationtest.AssertAnyChangeStrategy; import ch.ergon.adam.integrationtest.DummySink; import ch.ergon.adam.integrationtest.TestDbUrlProvider; -import ch.ergon.adam.core.db.SchemaDiffExtractor; -import ch.ergon.adam.core.db.schema.Schema; -import ch.ergon.adam.core.db.schema.Sequence; import org.junit.jupiter.api.Test; import static java.util.Arrays.asList; diff --git a/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/ViewTests.java b/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/ViewTests.java index 5f8d909..62700d0 100644 --- a/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/ViewTests.java +++ b/integration-test/src/test/java/ch/ergon/adam/integrationtest/testcases/ViewTests.java @@ -1,9 +1,9 @@ package ch.ergon.adam.integrationtest.testcases; +import ch.ergon.adam.core.db.schema.Schema; import ch.ergon.adam.integrationtest.AbstractDbTestBase; import ch.ergon.adam.integrationtest.DummySink; import ch.ergon.adam.integrationtest.TestDbUrlProvider; -import ch.ergon.adam.core.db.schema.Schema; import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.is; @@ -13,17 +13,17 @@ public abstract class ViewTests extends AbstractDbTestBase { private static final String CREATE_TABLE_SQL = - "create table test_table (" + - "id integer null " + + "create table \"test_table\" (" + + "\"id\" integer null " + ")"; private static final String CREATE_VIEW1_SQL = - "create view view1 as " + - "select * from test_table"; + "create view \"view1\" as " + + "select * from \"test_table\""; private static final String CREATE_VIEW2_SQL = - "create view view2 as " + - "select * from test_table"; + "create view \"view2\" as " + + "select * from \"test_table\""; public ViewTests(TestDbUrlProvider testDbUrlProvider) { super(testDbUrlProvider); diff --git a/jooq/build.gradle b/jooq/build.gradle index 05f631e..50a0437 100644 --- a/jooq/build.gradle +++ b/jooq/build.gradle @@ -11,7 +11,7 @@ sourceCompatibility = 21 dependencies { testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.7.1' api project(':core') - api group: 'org.jooq', name: 'jooq', version: '3.19.11' + implementation group: 'org.jooq', name: 'jooq', version: '3.19.11' } apply from: "${rootProject.projectDir}/publish.gradle" diff --git a/jooq/src/main/java/ch/ergon/adam/jooq/JooqSink.java b/jooq/src/main/java/ch/ergon/adam/jooq/JooqSink.java index 2953f2d..0cf90b6 100644 --- a/jooq/src/main/java/ch/ergon/adam/jooq/JooqSink.java +++ b/jooq/src/main/java/ch/ergon/adam/jooq/JooqSink.java @@ -51,7 +51,9 @@ public JooqSink(Connection connection, SQLDialect dialect) { protected JooqSink(Connection dbConnection, SQLDialect dialect, String schema) { context = DSL.using(dbConnection, dialect); - context.createSchemaIfNotExists(schema).execute(); + if (!dialect.family().name().contains("ORACLE")) { + context.createSchemaIfNotExists(schema).execute(); + } this.schema = schema; } @@ -94,7 +96,7 @@ public void dropForeignKey(ForeignKey foreignKey) { public void createForeignKey(ForeignKey foreignKey) { Index targetIndex = foreignKey.getTargetIndex(); org.jooq.Constraint constraint = DSL.constraint(foreignKey.getName()).foreignKey(foreignKey.getField().getName()) - .references(targetIndex.getTable().getName(), targetIndex.getFields().get(0).getName()); + .references(targetIndex.getTable().getName(), targetIndex.getFields().getFirst().getName()); context.alterTable(foreignKey.getTable().getName()).add(constraint).execute(); } @@ -250,12 +252,17 @@ public void dropSequencesAndDefaults(Table table) { .filter(Field::isSequence) .forEach(field -> context.alterTable(table.getName()) .alterColumn(field.getName()) - .defaultValue(DSL.field("null")) + .dropDefault() .execute()); //TODO: implement drop of sequences } + @Override + public void adjustSequences(Table table) { + + } + @Override public void renameTable(Table oldTable, String targetTableName) { context.alterTable(getTableName(oldTable)).renameTo(targetTableName).execute(); diff --git a/jooq/src/main/java/ch/ergon/adam/jooq/JooqSource.java b/jooq/src/main/java/ch/ergon/adam/jooq/JooqSource.java index 365a3d9..4064d70 100644 --- a/jooq/src/main/java/ch/ergon/adam/jooq/JooqSource.java +++ b/jooq/src/main/java/ch/ergon/adam/jooq/JooqSource.java @@ -1,6 +1,7 @@ package ch.ergon.adam.jooq; import ch.ergon.adam.core.db.interfaces.SchemaSource; +import ch.ergon.adam.core.db.schema.*; import ch.ergon.adam.core.db.schema.DataType; import ch.ergon.adam.core.db.schema.Field; import ch.ergon.adam.core.db.schema.ForeignKey; @@ -16,19 +17,19 @@ import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; +import java.util.stream.Stream; import static ch.ergon.adam.core.helper.CollectorsHelper.toLinkedMap; import static java.util.Arrays.stream; import static java.util.Comparator.comparing; import static java.util.function.Function.identity; import static java.util.stream.Collectors.toList; +import static org.jooq.TableOptions.TableType.TABLE; +import static org.jooq.TableOptions.TableType.VIEW; -public class JooqSource implements SchemaSource { +public abstract class JooqSource implements SchemaSource { private final Connection connection; private final String schemaName; @@ -65,7 +66,7 @@ protected void setSqlDialect(SQLDialect dialect) { protected Meta getMeta() { if (meta == null) { - meta = extractMeta(schemaName); + meta = JooqUtils.extractMeta(getContext(), schemaName); } return meta; } @@ -77,18 +78,6 @@ protected DSLContext getContext() { return context; } - private Meta extractMeta(String schemaName) { - if (schemaName == null) { - return getContext().meta(); - } - List schemas = getContext().meta().getSchemas(schemaName); - if (schemas.isEmpty()) { - String knownSchemas = getContext().meta().getSchemas().stream().map(Named::getName).collect(Collectors.joining(",")); - throw new RuntimeException("Schema [" + schemaName + "] not found. Known schemas are [" + knownSchemas + "]"); - } - return getContext().meta(schemas.get(0)); - } - @Override public void close() { if (context instanceof CloseableDSLContext) { @@ -104,12 +93,18 @@ public Schema getSchema() { Schema schema = new Schema(); schema.setTables(getTables()); + schema.setViews(getViews()); + setViewDependencies(schema); return schema; } private Collection getTables() { List> jooqTables = getMeta().getTables(); + jooqTables = jooqTables.stream() + .filter(table -> table.getOptions().type() == TABLE) + .toList(); + Map tables = jooqTables.stream() .map(this::mapTableFromJooq) .sorted(comparing(Table::getName)) @@ -119,6 +114,35 @@ private Collection
getTables() { return tables.values(); } + private Collection getViews() { + List> jooqTables = getMeta().getTables(); + + Map views = jooqTables.stream() + .filter(table -> table.getOptions().type() == VIEW) + .map(this::mapViewFromJooq) + .sorted(comparing(View::getName)) + .collect(toLinkedMap(View::getName, identity())); + return views.values(); + } + + private void setViewDependencies(Schema schema) { + Map> dependencies = fetchViewDependencies(); + schema.getViews().stream() + .filter(v -> dependencies.containsKey(v.getName())) + .forEach(v -> { + dependencies.get(v.getName()).stream().map(base -> { + Relation r = schema.getTable(base); + if (r == null) { + r = schema.getView(base); + } + return r; + }) + .filter(Objects::nonNull) + .forEach(v::addBaseRelation); + }); + + } + private void mapForeignKeys(List> jooqTables, Map tables) { for (org.jooq.Table jooqTable : jooqTables) { Table table = tables.get(jooqTable.getName()); @@ -129,7 +153,11 @@ private void mapForeignKeys(List> jooqTables, Map tables, org.jooq.ForeignKey jooqForeignKey) { Table table = tables.get(jooqForeignKey.getTable().getName()); - ForeignKey foreignKey = new ForeignKey(jooqForeignKey.getName()); + String name = jooqForeignKey.getName(); + if (isGeneratedName(name)) { + name = null; + } + ForeignKey foreignKey = new ForeignKey(name); if (jooqForeignKey.getFields().size() != 1) { throw new RuntimeException("Table [" + table.getName() + "] contains a foreign key over multiple fields. This is not yet supported."); } @@ -139,6 +167,10 @@ private ForeignKey mapForeignKeyFromJooq(Map tables, org.jooq.For return foreignKey; } + protected boolean isGeneratedName(String name) { + return false; + } + private Table mapTableFromJooq(org.jooq.Table jooqTable) { Table table = new Table(jooqTable.getName()); table.setFields(stream(jooqTable.fields()).map(this::mapFieldFromJooq).collect(toList())); @@ -151,6 +183,17 @@ private Table mapTableFromJooq(org.jooq.Table jooqTable) { return table; } + private View mapViewFromJooq(org.jooq.Table jooqTable) { + View view = new View(jooqTable.getName()); + view.setFields(stream(jooqTable.fields()).map(this::mapFieldFromJooq).collect(toList())); + view.setViewDefinition(getViewDefinition(view.getName())); + return view; + } + + abstract protected String getViewDefinition(String name); + + abstract protected Map> fetchViewDependencies(); + private Index mapPrimaryKeyFromJooq(Table table, UniqueKey primaryKey) { Index index = mapUniqueKeyFromJooq(table, primaryKey); index.setPrimary(true); @@ -169,17 +212,23 @@ private Index mapIndexFromJooq(Table table, org.jooq.Index jooqIndex) { index.setWhere(jooqIndex.getWhere().toString()); } index.setUnique(jooqIndex.getUnique()); - index.setFields(jooqIndex.getFields().stream().map(jooqField -> table.getField(jooqField.getName())).collect(toList())); + index.setFields(jooqIndex.getFields().stream() + .map(jooqField -> table.getField(jooqField.getName())) + .filter(Objects::nonNull) + .collect(toList())); return index; } private Index mapKeyFromJooq(Table table, Key jooqKey) { Index index = new Index(jooqKey.getName()); - index.setFields(jooqKey.getFields().stream().map(jooqField -> table.getField(jooqField.getName())).collect(toList())); + index.setFields(jooqKey.getFields().stream() + .map(jooqField -> table.getField(jooqField.getName())) + .filter(Objects::nonNull) + .collect(toList())); return index; } - private Field mapFieldFromJooq(org.jooq.Field jooqField) { + protected Field mapFieldFromJooq(org.jooq.Field jooqField) { Field field = new Field(jooqField.getName()); field.setArray(jooqField.getDataType().isArray()); field.setDataType(mapDataTypeFromJooq(jooqField)); @@ -198,7 +247,6 @@ private Field mapFieldFromJooq(org.jooq.Field jooqField) { field.setPrecision(elementType.hasPrecision() && elementType.precision() > 0 && elementType.precision() < 10000 ? elementType.precision() : null); field.setScale(elementType.hasScale() && elementType.scale() > 0 ? elementType.scale() : null); - field.setSequence(isSequence(jooqField)); if (!field.isSequence() && jooqField.getDataType().defaulted()) { field.setDefaultValue(getDefaultValue(jooqField)); diff --git a/jooq/src/main/java/ch/ergon/adam/jooq/JooqUtils.java b/jooq/src/main/java/ch/ergon/adam/jooq/JooqUtils.java new file mode 100644 index 0000000..b1dfa53 --- /dev/null +++ b/jooq/src/main/java/ch/ergon/adam/jooq/JooqUtils.java @@ -0,0 +1,28 @@ +package ch.ergon.adam.jooq; + +import org.jooq.DSLContext; +import org.jooq.Meta; +import org.jooq.Named; +import org.jooq.Schema; + +import java.util.List; +import java.util.stream.Collectors; + +public class JooqUtils { + + private JooqUtils() { + throw new UnsupportedOperationException(); + } + + public static Meta extractMeta(DSLContext context, String schemaName) { + if (schemaName == null) { + return context.meta(); + } + List schemas = context.meta().getSchemas(schemaName); + if (schemas.isEmpty()) { + String knownSchemas = context.meta().getSchemas().stream().map(Named::getName).collect(Collectors.joining(",")); + throw new RuntimeException("Schema [" + schemaName + "] not found. Known schemas are [" + knownSchemas + "]"); + } + return context.meta(schemas.get(0)); + } +} diff --git a/oracle/build.gradle b/oracle/build.gradle new file mode 100644 index 0000000..ee050c0 --- /dev/null +++ b/oracle/build.gradle @@ -0,0 +1,33 @@ +plugins { + id 'java-library' + id "ch.ergon.gradle.goodies.versioning" +} + +ext.jooqProUser = hasProperty("jooqProUser") ? property("jooqProUser").toString() : rootProject.getLocalProperty("JOOQ_PRO_USER") +ext.jooqProPassword = hasProperty("jooqProPassword") ? property("jooqProPassword").toString() : rootProject.getLocalProperty("JOOQ_PRO_PASSWORD") + +repositories { + mavenCentral() + maven { + url = uri("https://repo.jooq.org/repo") + credentials { + username = jooqProUser + password = jooqProPassword + } + } +} + +group 'ch.ergon.adam' +description 'The Oracle plugin for ADAM' + +sourceCompatibility = 21 + +dependencies { + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.7.1' + implementation project(':jooq') + implementation 'com.oracle.database.jdbc:ojdbc11:23.5.0.24.07' + implementation group: 'org.jooq.pro', name: 'jooq', version: '3.19.11' +} + +apply from: "${rootProject.projectDir}/publish.gradle" +apply from: "${rootProject.projectDir}/common.gradle" diff --git a/oracle/src/main/java/ch/ergon/adam/oracle/OracleScriptParser.java b/oracle/src/main/java/ch/ergon/adam/oracle/OracleScriptParser.java new file mode 100644 index 0000000..25f6eb6 --- /dev/null +++ b/oracle/src/main/java/ch/ergon/adam/oracle/OracleScriptParser.java @@ -0,0 +1,80 @@ +package ch.ergon.adam.oracle; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class OracleScriptParser { + + Pattern COMMENT_PATTERN = Pattern.compile("/\\*.*?\\*/"); + + private final List script; + + private String statementSoFar; + private int lineNo; + private int statementLineNo; + private List result; + + public OracleScriptParser(String sourceScript) { + script = Arrays.asList(sourceScript.split("\n")); + } + + public List parse() { + resetParser(); + for (String line : script) { + lineNo += 1; + String normalizedLine = line.trim().toLowerCase(); + if (normalizedLine.startsWith("--")) { + continue; + } else if (line.equals("/") || line.equals(";")) { + recognizeTerminator(); + } else { + recognizeSqlLine(line); + } + } + recognizeTerminator(); + return result; + } + + private void resetParser() { + result = new LinkedList<>(); + statementSoFar = ""; + lineNo = 0; + statementLineNo = 0; + } + + private void recognizeTerminator() { + String sql = stripSemicolonIfRequired(statementSoFar); + statementSoFar = ""; + if (!stripComments(sql).isEmpty()) { + result.add(new SqlStatement(sql, statementLineNo)); + } + } + + private String stripSemicolonIfRequired(String sql) { + sql = sql.trim(); + // only strip the semicolon at the very end, and only if it is not a BEGIN-END block + if (sql.endsWith(";") && !sql.toLowerCase().endsWith("end;")) { + sql = sql.substring(0, sql.length()-1); + } + return sql; + } + + private String stripComments(String sql) { + // strip /* ... */ comments + Matcher matcher = COMMENT_PATTERN.matcher(sql); + return matcher.replaceAll("").trim(); + } + + private void recognizeSqlLine(String line) { + if (statementSoFar == "") { + statementLineNo = lineNo; + } + statementSoFar += line + System.lineSeparator(); + } + + public record SqlStatement(String statement, int lineNo) {} +} + diff --git a/oracle/src/main/java/ch/ergon/adam/oracle/OracleSqlExecutor.java b/oracle/src/main/java/ch/ergon/adam/oracle/OracleSqlExecutor.java new file mode 100644 index 0000000..a1ca86e --- /dev/null +++ b/oracle/src/main/java/ch/ergon/adam/oracle/OracleSqlExecutor.java @@ -0,0 +1,26 @@ +package ch.ergon.adam.oracle; + +import ch.ergon.adam.jooq.JooqSqlExecutor; + +import java.sql.Connection; +import java.util.List; + +public class OracleSqlExecutor extends JooqSqlExecutor { + + public OracleSqlExecutor(String url, String schema) { + super(url, schema); + } + + public OracleSqlExecutor(Connection dbConnection, String schema) { + super(dbConnection, schema); + } + + @Override + public void executeScript(String script) { + OracleScriptParser parser = new OracleScriptParser(script); + List statements = parser.parse(); + for (OracleScriptParser.SqlStatement statement : statements) { + super.executeScript(statement.statement()); + } + } +} diff --git a/oracle/src/main/java/ch/ergon/adam/oracle/OracleSqlFactory.java b/oracle/src/main/java/ch/ergon/adam/oracle/OracleSqlFactory.java new file mode 100644 index 0000000..13616fb --- /dev/null +++ b/oracle/src/main/java/ch/ergon/adam/oracle/OracleSqlFactory.java @@ -0,0 +1,69 @@ +package ch.ergon.adam.oracle; + +import ch.ergon.adam.core.db.interfaces.SchemaSink; +import ch.ergon.adam.core.db.interfaces.SchemaSource; +import ch.ergon.adam.core.db.interfaces.SourceAndSinkAdapter; +import ch.ergon.adam.core.db.interfaces.SqlExecutor; + +import java.util.HashMap; +import java.util.Map; + +public class OracleSqlFactory implements SourceAndSinkAdapter { + + private static Map sqlSinksByUrl = new HashMap<>(); + + @Override + public boolean supportsUrl(String url) { + return url.toLowerCase().startsWith("jdbc:oracle:"); + } + + @Override + public SchemaSource createSource(String url) { + return getTransactionWrapper(url); + } + + @Override + public SchemaSink createSink(String url) { + return getTransactionWrapper(url); + } + + @Override + public SqlExecutor createSqlExecutor(String url) { + return getTransactionWrapper(url); + } + + public static synchronized OracleSqlTransactionWrapper getTransactionWrapper(String url) { + if (!sqlSinksByUrl.containsKey(url) || sqlSinksByUrl.get(url).isClosed()) { + sqlSinksByUrl.put(url, new OracleSqlTransactionWrapper(url, extractSchema(url), () -> closeConnection(url))); + } + OracleSqlTransactionWrapper connection = sqlSinksByUrl.get(url); + connection.increaseClientCount(); + return connection; + } + + public static void closeConnection(String url) { + OracleSqlTransactionWrapper connection = sqlSinksByUrl.get(url); + if (connection == null) { + return; + } + connection.decreaseClientCount(); + if (connection.getClientCount() > 0) { + return; + } + sqlSinksByUrl.get(url).reallyClose(); + sqlSinksByUrl.remove(url); + } + + private static String extractSchema(String url) { + int idx = url.indexOf("currentSchema="); + if (idx < 0) { + return "public"; + } + idx += "currentSchema=".length(); + int endIdx = url.indexOf("&", idx); + if (endIdx < 0) { + return url.substring(idx); + } + return url.substring(idx, endIdx); + } +} diff --git a/oracle/src/main/java/ch/ergon/adam/oracle/OracleSqlSink.java b/oracle/src/main/java/ch/ergon/adam/oracle/OracleSqlSink.java new file mode 100644 index 0000000..faecd66 --- /dev/null +++ b/oracle/src/main/java/ch/ergon/adam/oracle/OracleSqlSink.java @@ -0,0 +1,37 @@ +package ch.ergon.adam.oracle; + +import ch.ergon.adam.core.db.schema.Field; +import ch.ergon.adam.core.db.schema.Table; +import ch.ergon.adam.jooq.JooqSink; + +import java.sql.Connection; + +import static java.lang.String.format; +import static org.jooq.SQLDialect.ORACLE18C; + +public class OracleSqlSink extends JooqSink { + + private String schemaName; + + public OracleSqlSink(Connection dbConnection, String schema) { + super(dbConnection, ORACLE18C, schema); + this.schemaName = schema; + } + + @Override + public void dropSequencesAndDefaults(Table table) { + //TODO: implement drop of sequences + } + + @Override + public void adjustSequences(Table table) { + table.getFields().stream() + .filter(Field::isSequence) + .forEach(this::adjustSequence); + } + + private void adjustSequence(Field field) { + Long nextValue = context.resultQuery(format("SELECT NVL(max(\"%s\"), 0) + 1 FROM \"%s\"", field.getName(), field.getTable().getName())).fetchSingle(0, Long.class);; + context.query(format("alter table \"%s\" modify \"%s\" generated by default on null as identity(start with %d)", field.getTable().getName(), field.getName(), nextValue)).execute(); + } +} diff --git a/oracle/src/main/java/ch/ergon/adam/oracle/OracleSqlSource.java b/oracle/src/main/java/ch/ergon/adam/oracle/OracleSqlSource.java new file mode 100644 index 0000000..82889f2 --- /dev/null +++ b/oracle/src/main/java/ch/ergon/adam/oracle/OracleSqlSource.java @@ -0,0 +1,120 @@ +package ch.ergon.adam.oracle; + +import ch.ergon.adam.core.db.schema.*; +import ch.ergon.adam.jooq.JooqSource; +import org.jooq.Record; +import org.jooq.Result; +import org.jooq.SQLDialect; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; + +import static java.lang.String.format; +import static java.util.Comparator.comparing; +import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.*; + +public class OracleSqlSource extends JooqSource { + + + private final String schemaName; + private Map enums; + + public OracleSqlSource(String url, String schemaName) throws SQLException { + super(url, schemaName); + this.schemaName = schemaName; + this.setSqlDialect(SQLDialect.ORACLE18C); + } + + public OracleSqlSource(Connection connection, String schemaName) { + super(connection, schemaName); + this.setSqlDialect(SQLDialect.ORACLE18C); + this.schemaName = schemaName; + } + + @Override + public Schema getSchema() { + Schema schema = super.getSchema(); + fetchConstraints(schema); + return schema; + } + + @Override + protected String getViewDefinition(String name) { + Result result = getContext().resultQuery("select TEXT from SYS.USER_VIEWS where VIEW_NAME = ?", name).fetch(); + return result.getFirst().getValue("TEXT").toString(); + } + + @Override + protected Map> fetchViewDependencies() { + Result result = getContext().resultQuery( + """ + SELECT NAME AS owner, REFERENCED_NAME AS base + FROM SYS.ALL_DEPENDENCIES + WHERE TYPE = 'VIEW' AND REFERENCED_TYPE IN ('VIEW', 'TABLE') + AND OWNER = ? + AND REFERENCED_OWNER = OWNER + """, schemaName).fetch(); + + return result.stream() + .collect( + groupingBy(r -> r.getValue("OWNER").toString(), + mapping(r -> r.getValue("BASE").toString(), toList()))); + } + + private void fetchConstraints(Schema schema) { + Result result = getContext().resultQuery( + """ + SELECT CONSTRAINT_NAME, CONSTRAINT_TYPE, TABLE_NAME, SEARCH_CONDITION FROM ALL_CONSTRAINTS WHERE OWNER = ? + AND SEARCH_CONDITION_VC not like ('"%" IS NOT NULL') + """, schemaName).fetch(); + + Map> byTable = result.stream() + .filter(r -> schema.getTable(r.getValue("TABLE_NAME").toString()) != null) + .collect(groupingBy( + r -> schema.getTable(r.getValue("TABLE_NAME").toString()), + toList())); + + byTable.keySet().forEach(table -> + table.setConstraints(byTable.get(table).stream() + .map(this::mapConstraintFromOracle) + .collect(toList()))); + } + + private Constraint mapConstraintFromOracle(Record record) { + String constraintType = record.getValue("CONSTRAINT_TYPE").toString(); + String name = record.getValue("CONSTRAINT_NAME").toString(); + if (isGeneratedName(name)) { + name = null; + } + switch (constraintType) { + case "P": + return new PrimaryKeyConstraint(name); + case "C": + RuleConstraint ruleConstraint = new RuleConstraint(name); + String expression = record.getValue("SEARCH_CONDITION").toString(); + ruleConstraint.setRule(expression); + return ruleConstraint; + default: + throw new RuntimeException(format("Unsupported constraint type [%s]", constraintType)); + } + } + + @Override + protected boolean isGeneratedName(String name) { + return name.startsWith("SYS_"); + } + + @Override + protected Field mapFieldFromJooq(org.jooq.Field jooqField) { + Field field = super.mapFieldFromJooq(jooqField); + if (field.getDataType() == DataType.DECIMAL_INTEGER && field.getPrecision() == 19) { + field.setPrecision(null); + field.setDataType(DataType.BIGINT); + } + return field; + } +} diff --git a/oracle/src/main/java/ch/ergon/adam/oracle/OracleSqlTransactionWrapper.java b/oracle/src/main/java/ch/ergon/adam/oracle/OracleSqlTransactionWrapper.java new file mode 100644 index 0000000..52bb20a --- /dev/null +++ b/oracle/src/main/java/ch/ergon/adam/oracle/OracleSqlTransactionWrapper.java @@ -0,0 +1,267 @@ +package ch.ergon.adam.oracle; + +import ch.ergon.adam.core.db.schema.*; +import ch.ergon.adam.jooq.JooqSqlExecutor; +import ch.ergon.adam.core.db.interfaces.SchemaSink; +import ch.ergon.adam.core.db.interfaces.SchemaSource; +import ch.ergon.adam.core.db.interfaces.SqlExecutor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +public class OracleSqlTransactionWrapper implements SchemaSource, SchemaSink, SqlExecutor { + + private static final Logger logger = LoggerFactory.getLogger(OracleSqlTransactionWrapper.class); + + private final Connection dbConnection; + private final Runnable closeHandler; + JooqSqlExecutor sqlExecutor; + OracleSqlSink sqlSink; + OracleSqlSource sqlSource; + private boolean closed; + private int clientCount = 0; + + public OracleSqlTransactionWrapper(String url, String schema, Runnable closeHandler) { + this.closeHandler = closeHandler; + try { + dbConnection = DriverManager.getConnection(url); + sqlSink = new OracleSqlSink(dbConnection, schema); + sqlSource = new OracleSqlSource(dbConnection, schema); + beginTransaction(); + sqlExecutor = new OracleSqlExecutor(dbConnection, schema); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + public void increaseClientCount() { + clientCount++; + } + + public void decreaseClientCount() { + clientCount--; + } + + public int getClientCount() { + return clientCount; + } + + public void beginTransaction() { + try { + dbConnection.setAutoCommit(false); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + public void commitTransaction() { + try { + logger.info("Doing a commit of the changes."); + dbConnection.commit(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + public void rollbackTransaction() { + try { + if (dbConnection.isClosed()) { + return; + } + logger.info("Doing a rollback of the changes."); + dbConnection.rollback(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + @Override + public void setTargetSchema(Schema targetSchema) { + sqlSink.setTargetSchema(targetSchema); + } + + @Override + public void commitChanges() { + sqlSink.commitChanges(); + } + + @Override + public void rollback() { + rollbackTransaction(); + } + + @Override + public void dropForeignKey(ForeignKey foreignKey) { + sqlSink.dropForeignKey(foreignKey); + } + + @Override + public void createForeignKey(ForeignKey foreignKey) { + sqlSink.createForeignKey(foreignKey); + } + + @Override + public void dropIndex(Index index) { + sqlSink.dropIndex(index); + } + + @Override + public void createIndex(Index index) { + sqlSink.createIndex(index); + } + + @Override + public void addField(Field field) { + sqlSink.addField(field); + } + + @Override + public void dropField(Field field, Table table) { + sqlSink.dropField(field, table); + } + + @Override + public void setDefault(Field field) { + sqlSink.setDefault(field); + } + + @Override + public void dropDefault(Field field) { + sqlSink.dropDefault(field); + } + + @Override + public void createTable(Table table) { + sqlSink.createTable(table); + } + + @Override + public void dropTable(Table table) { + sqlSink.dropTable(table); + } + + @Override + public void renameTable(Table oldTable, String targetTableName) { + sqlSink.renameTable(oldTable, targetTableName); + } + + @Override + public void copyData(Table sourceTable, Table targetTable, String sourceTableName) { + sqlSink.copyData(sourceTable, targetTable, sourceTableName); + } + + @Override + public void createView(View view) { + sqlSink.createView(view); + } + + @Override + public void dropView(View view) { + sqlSink.dropView(view); + } + + @Override + public void dropEnum(DbEnum dbEnum) { + sqlSink.dropEnum(dbEnum); + } + + @Override + public void createEnum(DbEnum dbEnum) { + sqlSink.createEnum(dbEnum); + } + + @Override + public void changeFieldType(Field oldField, Field newField, DataType targetDataType) { + sqlSink.changeFieldType(oldField, newField, targetDataType); + } + + @Override + public void dropConstraint(Constraint constraint) { + sqlSink.dropConstraint(constraint); + } + + @Override + public void createConstraint(Constraint constraint) { + sqlSink.createConstraint(constraint); + } + + @Override + public void dropSequence(Sequence sequence) { + sqlSink.dropSequence(sequence); + } + + @Override + public void createSequence(Sequence sequence) { + sqlSink.createSequence(sequence); + } + + @Override + public void dropSequencesAndDefaults(Table table) { + sqlSink.dropSequencesAndDefaults(table); + } + + @Override + public void adjustSequences(Table table) { + sqlSink.adjustSequences(table); + } + + @Override + public void executeScript(String script) { + sqlExecutor.executeScript(script); + } + + @Override + public Object queryResult(String query, Object... params) { + return sqlExecutor.queryResult(query, params); + } + + @Override + public void dropSchema() { + sqlExecutor.dropSchema(); + } + + @Override + public void close() { + closeHandler.run(); + } + + public void reallyClose() { + this.closed = true; + try { + if (!dbConnection.getAutoCommit()) { + commitTransaction(); + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + sqlExecutor.close(); + sqlSink.close(); + sqlSource.close(); + try { + dbConnection.close(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + public boolean isClosed() { + return closed; + } + + @Override + public boolean supportAlterAndDropField() { + return sqlSink.supportAlterAndDropField(); + } + + @Override + public Schema getSchema() { + return sqlSource.getSchema(); + } + + public Connection getConnection() { + return dbConnection; + } +} diff --git a/postgresql/build.gradle b/postgresql/build.gradle index f830241..cae92df 100644 --- a/postgresql/build.gradle +++ b/postgresql/build.gradle @@ -12,6 +12,7 @@ dependencies { testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.7.1' implementation project(':jooq') implementation group: 'org.postgresql', name: 'postgresql', version: '42.2.19' + implementation group: 'org.jooq', name: 'jooq', version: '3.19.11' } apply from: "${rootProject.projectDir}/publish.gradle" diff --git a/postgresql/src/main/java/ch/ergon/adam/postgresql/PostgreSqlSource.java b/postgresql/src/main/java/ch/ergon/adam/postgresql/PostgreSqlSource.java index 152fb88..3c4e7d2 100644 --- a/postgresql/src/main/java/ch/ergon/adam/postgresql/PostgreSqlSource.java +++ b/postgresql/src/main/java/ch/ergon/adam/postgresql/PostgreSqlSource.java @@ -13,6 +13,7 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; import static ch.ergon.adam.core.db.schema.DataType.ENUM; import static ch.ergon.adam.core.helper.CollectorsHelper.toLinkedMap; @@ -20,8 +21,7 @@ import static java.util.Comparator.comparing; import static java.util.Objects.requireNonNull; import static java.util.function.Function.identity; -import static java.util.stream.Collectors.groupingBy; -import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.*; public class PostgreSqlSource extends JooqSource { @@ -51,59 +51,11 @@ public Schema getSchema() { Schema schema = super.getSchema(); schema.setSequences(getSequences()); schema.setEnums(enums.values()); - schema.setViews(getViews()); - schema.getViews().forEach(view -> view.setFields(schema.getTable(view.getName()).getFields())); - schema.getTables().removeIf(table -> schema.getView(table.getName()) != null); - fetchDefaults(schema); setCustomTypes(schema); fetchConstraints(schema); - fetchViewDependencies(schema); return schema; } - private void fetchDefaults(Schema schema) { - //TODO: Remove as soon as https://github.com/jOOQ/jOOQ/issues/8875 is fixed - - Result result = getContext().resultQuery( - "SELECT column_name, column_default, table_name\n" + - "FROM information_schema.columns where table_schema = ? and column_default is not null", schemaName).fetch(); - - for (Record record : result) { - String tableName = record.getValue("table_name").toString(); - String columnName = record.getValue("column_name").toString(); - String defaultValue = record.getValue("column_default").toString(); - - Table table = schema.getTable(tableName); - if (table == null) { - continue; - } - - Field field = table.getField(columnName); - if (field == null || field.isSequence() || (field.getDefaultValue() != null && field.getDefaultValue() != "null")) { - continue; - } - - field.setDefaultValue(defaultValue); - } - - for (Table table : schema.getTables()) { - for (Field field : table.getFields()) { - String defaultValue = field.getDefaultValue(); - if (defaultValue == null) { - continue; - } - - if (field.isArray() && defaultValue.endsWith("[]")) { - defaultValue = defaultValue.substring(0, defaultValue.length() - 2); - } - - field.setDefaultValue(defaultValue.replaceAll("::[a-z A-Z_]*", "")); - } - - } - - } - @Override protected String getDefaultValue(org.jooq.Field jooqField) { String defaultValue = super.getDefaultValue(jooqField); @@ -115,9 +67,10 @@ protected String getDefaultValue(org.jooq.Field jooqField) { return defaultValue; } - private void fetchViewDependencies(Schema schema) { + @Override + protected Map> fetchViewDependencies() { Result result = getContext().resultQuery( - "SELECT cl_r.relname AS view, cl_d.relname AS base, cl_d.relkind basetype " + + "SELECT cl_r.relname AS view, cl_d.relname AS base " + "FROM pg_rewrite AS r " + "JOIN pg_class AS cl_r ON r.ev_class=cl_r.oid " + "JOIN pg_depend AS d ON r.oid = d.objid " + @@ -126,15 +79,10 @@ private void fetchViewDependencies(Schema schema) { "WHERE cl_d.relkind IN ('v', 'r') and cl_r.relname != cl_d.relname AND ns.nspname = ? " + "GROUP BY cl_r.relname, cl_d.relname, cl_d.relkind", schemaName).fetch(); - for (Record record : result) { - String viewName = record.getValue("view").toString(); - String baseName = record.getValue("base").toString(); - String baseType = record.getValue("basetype").toString(); - - View view = schema.getView(viewName); - Relation relation = "v".equals(baseType) ? schema.getView(baseName) : schema.getTable(baseName); - view.addBaseRelation(relation); - } + return result.stream() + .collect( + groupingBy(r -> r.getValue("view").toString(), + mapping(r -> r.getValue("base").toString(), toList()))); } private void setCustomTypes(Schema schema) { @@ -235,17 +183,10 @@ private DbEnum mapEnumFromPostgres(Record record) { return dbEnum; } - private Collection getViews() { - Result result = getContext().resultQuery("select table_name, view_definition from INFORMATION_SCHEMA.views where table_schema = ?", schemaName).fetch(); - return result.stream().map(this::mapViewFromJooq).sorted(comparing(View::getName)).collect(toList()); - } - - private View mapViewFromJooq(Record record) { - String viewName = record.getValue("table_name").toString(); - String viewDefinition = record.getValue("view_definition").toString(); - View view = new View(viewName); - view.setViewDefinition(viewDefinition); - return view; + @Override + protected String getViewDefinition(String name) { + Result result = getContext().resultQuery("select view_definition from INFORMATION_SCHEMA.views where table_schema = ? and table_name = ?", schemaName, name).fetch(); + return result.getFirst().getValue("view_definition").toString(); } @Override diff --git a/postgresql/src/main/java/ch/ergon/adam/postgresql/PostgreSqlTransactionWrapper.java b/postgresql/src/main/java/ch/ergon/adam/postgresql/PostgreSqlTransactionWrapper.java index 7f5110d..9bf309b 100644 --- a/postgresql/src/main/java/ch/ergon/adam/postgresql/PostgreSqlTransactionWrapper.java +++ b/postgresql/src/main/java/ch/ergon/adam/postgresql/PostgreSqlTransactionWrapper.java @@ -203,6 +203,11 @@ public void dropSequencesAndDefaults(Table table) { sqlSink.dropSequencesAndDefaults(table); } + @Override + public void adjustSequences(Table table) { + sqlSink.adjustSequences(table); + } + @Override public void executeScript(String script) { sqlExecutor.executeScript(script); diff --git a/settings.gradle b/settings.gradle index b6eea88..0f17bdb 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,6 +3,7 @@ include 'core' include 'jooq' include 'yml' include 'postgresql' +include 'oracle' include 'integration-test' include 'sqlite' include 'gradle-plugin' diff --git a/sqlite/build.gradle b/sqlite/build.gradle index 36d909d..1974419 100644 --- a/sqlite/build.gradle +++ b/sqlite/build.gradle @@ -12,6 +12,7 @@ dependencies { testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.7.1' implementation project(':jooq') implementation group: 'org.xerial', name: 'sqlite-jdbc', version: '3.34.0' + implementation group: 'org.jooq', name: 'jooq', version: '3.19.11' } apply from: "${rootProject.projectDir}/publish.gradle" diff --git a/sqlite/src/main/java/ch/ergon/adam/sqlite/SqliteSource.java b/sqlite/src/main/java/ch/ergon/adam/sqlite/SqliteSource.java index dd80135..8822ac6 100644 --- a/sqlite/src/main/java/ch/ergon/adam/sqlite/SqliteSource.java +++ b/sqlite/src/main/java/ch/ergon/adam/sqlite/SqliteSource.java @@ -13,6 +13,7 @@ import java.sql.SQLException; import java.util.Collection; import java.util.List; +import java.util.Map; import static com.google.common.collect.Lists.newArrayList; import static java.util.Comparator.comparing; @@ -35,25 +36,21 @@ public SqliteSource(Connection connection) { @Override public Schema getSchema() { Schema schema = super.getSchema(); - schema.setViews(getViews()); - schema.getViews().forEach(view -> view.setFields(schema.getTable(view.getName()).getFields())); - schema.getTables().removeIf(table -> schema.getView(table.getName()) != null); setSequences(schema); return schema; } - private Collection getViews() { - Result result = getContext().resultQuery("select name, sql from sqlite_master where type = 'view'").fetch(); - return result.stream().map(this::mapViewFromJooq).sorted(comparing(View::getName)).collect(toList()); + @Override + protected String getViewDefinition(String name) { + Result result = getContext().resultQuery("select sql from sqlite_master where type = 'view' and name = ?", name).fetch(); + String viewDefinition = result.getFirst().getValue("sql").toString(); + viewDefinition = viewDefinition.replaceAll("^(?i)create view [^ ]+ as ", ""); + return viewDefinition; } - private View mapViewFromJooq(Record record) { - String viewName = record.getValue("name").toString(); - String viewDefinition = record.getValue("sql").toString(); - viewDefinition = viewDefinition.replaceAll("^(?i)create view [^ ]+ as ", ""); - View view = new View(viewName); - view.setViewDefinition(viewDefinition); - return view; + @Override + protected Map> fetchViewDependencies() { + return Map.of(); } private void setSequences(Schema schema) { diff --git a/yml/src/main/java/ch/ergon/adam/yml/YmlSink.java b/yml/src/main/java/ch/ergon/adam/yml/YmlSink.java index 5510bf1..6b51cd0 100644 --- a/yml/src/main/java/ch/ergon/adam/yml/YmlSink.java +++ b/yml/src/main/java/ch/ergon/adam/yml/YmlSink.java @@ -194,6 +194,11 @@ public void dropSequencesAndDefaults(Table table) { // Noop } + @Override + public void adjustSequences(Table table) { + // Noop + } + private void writeTableToFile(Table table) throws IOException { YmlTable ymlTable = mapToYml(table); writeToFileOrStream(ymlTable, Helper.getTableFileName(table));