From 61dbbf7c454fd1028203521ceae99c5a4e0afc79 Mon Sep 17 00:00:00 2001 From: Robin Arnold Date: Mon, 1 Nov 2021 23:46:51 +0000 Subject: [PATCH 1/5] issue 2751 make sure drop schema drops everything Signed-off-by: Robin Arnold --- .../database/utils/api/IDatabaseAdapter.java | 8 +++ .../database/utils/common/ListTablesDAO.java | 16 +++++ .../utils/common/SchemaInfoObject.java | 49 +++++++++++++++ .../fhir/database/utils/db2/Db2Adapter.java | 9 +++ .../utils/db2/Db2ListTablesForSchema.java | 57 +++++++++++++++++ .../database/utils/derby/DerbyAdapter.java | 10 +++ .../utils/derby/DerbyListTablesForSchema.java | 62 +++++++++++++++++++ .../utils/postgres/PostgresAdapter.java | 9 +++ .../postgres/PostgresListTablesForSchema.java | 60 ++++++++++++++++++ .../version/CreateWholeSchemaVersion.java | 18 ++++++ .../java/com/ibm/fhir/schema/app/Main.java | 35 ++++++++++- .../fhir/schema/derby/DerbyFhirDatabase.java | 8 +++ .../schema/derby/DerbyFhirDatabaseTest.java | 16 ++++- 13 files changed, 353 insertions(+), 4 deletions(-) create mode 100644 fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/common/ListTablesDAO.java create mode 100644 fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/common/SchemaInfoObject.java create mode 100644 fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/db2/Db2ListTablesForSchema.java create mode 100644 fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyListTablesForSchema.java create mode 100644 fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresListTablesForSchema.java diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/api/IDatabaseAdapter.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/api/IDatabaseAdapter.java index d56a4abc753..7f0cd999168 100644 --- a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/api/IDatabaseAdapter.java +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/api/IDatabaseAdapter.java @@ -10,6 +10,7 @@ import java.util.List; import java.util.function.Supplier; +import com.ibm.fhir.database.utils.common.SchemaInfoObject; import com.ibm.fhir.database.utils.model.CheckConstraint; import com.ibm.fhir.database.utils.model.ColumnBase; import com.ibm.fhir.database.utils.model.IdentityDef; @@ -584,4 +585,11 @@ public default boolean useSessionVariable() { * @param selectClause */ public void createOrReplaceView(String schemaName, String objectName, String selectClause); + + /** + * List the objects present in the given schema + * @param schemaName + * @return + */ + List listSchemaObjects(String schemaName); } \ No newline at end of file diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/common/ListTablesDAO.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/common/ListTablesDAO.java new file mode 100644 index 00000000000..0d756e6386c --- /dev/null +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/common/ListTablesDAO.java @@ -0,0 +1,16 @@ +/* + * (C) Copyright IBM Corp. 2021 + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.ibm.fhir.database.utils.common; + +import com.ibm.fhir.database.utils.api.IDatabaseSupplier; + +/** + * A DAO to list the tables in a given schema + */ +public interface ListTablesDAO extends IDatabaseSupplier{ + +} diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/common/SchemaInfoObject.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/common/SchemaInfoObject.java new file mode 100644 index 00000000000..9be9bb6e1ad --- /dev/null +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/common/SchemaInfoObject.java @@ -0,0 +1,49 @@ +/* + * (C) Copyright IBM Corp. 2021 + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.ibm.fhir.database.utils.common; + + +/** + * A database object (table, index, view etc) existing within a schema + */ +public class SchemaInfoObject { + public static enum Type { + TABLE, + INDEX, + PROCEDURE, + VIEW + } + + // The object type + private final Type type; + + // The object name + private final String name; + + public SchemaInfoObject(Type type, String name) { + this.type = type; + this.name = name; + } + + @Override + public String toString() { + return type.name() + ":" + name; + } + /** + * @return the type + */ + public Type getType() { + return type; + } + + /** + * @return the name + */ + public String getName() { + return name; + } +} \ No newline at end of file diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/db2/Db2Adapter.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/db2/Db2Adapter.java index 12b6fd03527..57780cccd2d 100644 --- a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/db2/Db2Adapter.java +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/db2/Db2Adapter.java @@ -35,6 +35,7 @@ import com.ibm.fhir.database.utils.common.CommonDatabaseAdapter; import com.ibm.fhir.database.utils.common.DataDefinitionUtil; import com.ibm.fhir.database.utils.common.DropColumn; +import com.ibm.fhir.database.utils.common.SchemaInfoObject; import com.ibm.fhir.database.utils.model.CheckConstraint; import com.ibm.fhir.database.utils.model.ColumnBase; import com.ibm.fhir.database.utils.model.IdentityDef; @@ -639,4 +640,12 @@ public void reorgTable(String schemaName, String tableName) { Db2Reorg cmd = new Db2Reorg(schemaName, tableName); runStatement(cmd); } + + @Override + public List listSchemaObjects(String schemaName) { + List result = new ArrayList<>(); + Db2ListTablesForSchema listTables = new Db2ListTablesForSchema(schemaName); + result.addAll(runStatement(listTables)); + return result; + } } \ No newline at end of file diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/db2/Db2ListTablesForSchema.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/db2/Db2ListTablesForSchema.java new file mode 100644 index 00000000000..1b6e3c996bf --- /dev/null +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/db2/Db2ListTablesForSchema.java @@ -0,0 +1,57 @@ +/* + * (C) Copyright IBM Corp. 2019, 2020 + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.ibm.fhir.database.utils.db2; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import com.ibm.fhir.database.utils.api.IDatabaseSupplier; +import com.ibm.fhir.database.utils.api.IDatabaseTranslator; +import com.ibm.fhir.database.utils.common.DataDefinitionUtil; +import com.ibm.fhir.database.utils.common.SchemaInfoObject; +import com.ibm.fhir.database.utils.common.SchemaInfoObject.Type; + +/** + * DAO to fetch the names of tables in the given schema + */ +public class Db2ListTablesForSchema implements IDatabaseSupplier> { + + // The schema of the table + private final String schemaName; + + /** + * Public constructor + * @param schemaName + */ + public Db2ListTablesForSchema(String schemaName) { + this.schemaName = DataDefinitionUtil.assertValidName(schemaName); + } + + @Override + public List run(IDatabaseTranslator translator, Connection c) { + List result = new ArrayList<>(); + // Grab the list of tables for the configured schema from the DB2 catalog + final String sql = "SELECT tabname FROM SYSCAT.TABLES WHERE tabschema = ?"; + + try (PreparedStatement ps = c.prepareStatement(sql)) { + ps.setString(1, schemaName); + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + result.add(new SchemaInfoObject(Type.TABLE, rs.getString(1))); + } + } + catch (SQLException x) { + throw translator.translate(x); + } + + return result; + } +} diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyAdapter.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyAdapter.java index f19e73cea09..7de63722e3b 100644 --- a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyAdapter.java +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyAdapter.java @@ -6,6 +6,7 @@ package com.ibm.fhir.database.utils.derby; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; @@ -24,6 +25,7 @@ import com.ibm.fhir.database.utils.common.CommonDatabaseAdapter; import com.ibm.fhir.database.utils.common.DataDefinitionUtil; import com.ibm.fhir.database.utils.common.GetSequenceNextValueDAO; +import com.ibm.fhir.database.utils.common.SchemaInfoObject; import com.ibm.fhir.database.utils.model.CheckConstraint; import com.ibm.fhir.database.utils.model.ColumnBase; import com.ibm.fhir.database.utils.model.ForeignKeyConstraint; @@ -367,4 +369,12 @@ public void createOrReplaceView(String schemaName, String viewName, String selec } createView(schemaName, viewName, selectClause); } + + @Override + public List listSchemaObjects(String schemaName) { + List result = new ArrayList<>(); + DerbyListTablesForSchema dao = new DerbyListTablesForSchema(schemaName); + result.addAll(runStatement(dao)); + return result; + } } \ No newline at end of file diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyListTablesForSchema.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyListTablesForSchema.java new file mode 100644 index 00000000000..b1fd7aca6f4 --- /dev/null +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyListTablesForSchema.java @@ -0,0 +1,62 @@ +/* + * (C) Copyright IBM Corp. 2019, 2020 + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.ibm.fhir.database.utils.derby; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import com.ibm.fhir.database.utils.api.IDatabaseSupplier; +import com.ibm.fhir.database.utils.api.IDatabaseTranslator; +import com.ibm.fhir.database.utils.common.DataDefinitionUtil; +import com.ibm.fhir.database.utils.common.SchemaInfoObject; +import com.ibm.fhir.database.utils.common.SchemaInfoObject.Type; + +/** + * DAO to fetch the names of tables in the given schema + */ +public class DerbyListTablesForSchema implements IDatabaseSupplier> { + + // The schema of the table + private final String schemaName; + + /** + * Public constructor + * @param schemaName + */ + public DerbyListTablesForSchema(String schemaName) { + this.schemaName = DataDefinitionUtil.assertValidName(schemaName); + } + + @Override + public List run(IDatabaseTranslator translator, Connection c) { + List result = new ArrayList<>(); + // Grab the list of tables for the configured schema from the Derby sys catalog + final String sql = "" + + "SELECT tables.tablename FROM sys.systables AS tables " + + " JOIN sys.sysschemas AS schemas " + + " ON (tables.schemaid = schemas.schemaid) " + + " WHERE schemas.schemaname = ?" + ; + + try (PreparedStatement ps = c.prepareStatement(sql)) { + ps.setString(1, schemaName); + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + result.add(new SchemaInfoObject(Type.TABLE, rs.getString(1))); + } + } + catch (SQLException x) { + throw translator.translate(x); + } + + return result; + } +} \ No newline at end of file diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresAdapter.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresAdapter.java index defd3aee640..484a7d3cc45 100644 --- a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresAdapter.java +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresAdapter.java @@ -28,6 +28,7 @@ import com.ibm.fhir.database.utils.common.AddForeignKeyConstraint; import com.ibm.fhir.database.utils.common.CommonDatabaseAdapter; import com.ibm.fhir.database.utils.common.DataDefinitionUtil; +import com.ibm.fhir.database.utils.common.SchemaInfoObject; import com.ibm.fhir.database.utils.model.CheckConstraint; import com.ibm.fhir.database.utils.model.ColumnBase; import com.ibm.fhir.database.utils.model.ForeignKeyConstraint; @@ -454,4 +455,12 @@ public void createOrReplaceFunction(String schemaName, String functionName, Supp } super.createOrReplaceFunction(schemaName, functionName, supplier); } + + @Override + public List listSchemaObjects(String schemaName) { + List result = new ArrayList<>(); + PostgresListTablesForSchema dao = new PostgresListTablesForSchema(schemaName); + result.addAll(runStatement(dao)); + return result; + } } \ No newline at end of file diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresListTablesForSchema.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresListTablesForSchema.java new file mode 100644 index 00000000000..5cd85c699db --- /dev/null +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresListTablesForSchema.java @@ -0,0 +1,60 @@ +/* + * (C) Copyright IBM Corp. 2019, 2020 + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.ibm.fhir.database.utils.postgres; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import com.ibm.fhir.database.utils.api.IDatabaseSupplier; +import com.ibm.fhir.database.utils.api.IDatabaseTranslator; +import com.ibm.fhir.database.utils.common.DataDefinitionUtil; +import com.ibm.fhir.database.utils.common.SchemaInfoObject; +import com.ibm.fhir.database.utils.common.SchemaInfoObject.Type; + +/** + * DAO to fetch the names of tables in the given schema + */ +public class PostgresListTablesForSchema implements IDatabaseSupplier> { + + // The schema of the table + private final String schemaName; + + /** + * Public constructor + * @param schemaName + */ + public PostgresListTablesForSchema(String schemaName) { + this.schemaName = DataDefinitionUtil.assertValidName(schemaName); + } + + @Override + public List run(IDatabaseTranslator translator, Connection c) { + List result = new ArrayList<>(); + // Grab the list of tables for the configured schema from the PostgreSQL schema + // catalog + final String sql = "" + + "SELECT table_name FROM information_schema.tables " + + " WHERE table_schema = ?"; + + try (PreparedStatement ps = c.prepareStatement(sql)) { + ps.setString(1, schemaName); + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + result.add(new SchemaInfoObject(Type.TABLE, rs.getString(1))); + } + } + catch (SQLException x) { + throw translator.translate(x); + } + + return result; + } +} \ No newline at end of file diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/version/CreateWholeSchemaVersion.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/version/CreateWholeSchemaVersion.java index 8a77c851f09..acad4246cf3 100644 --- a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/version/CreateWholeSchemaVersion.java +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/version/CreateWholeSchemaVersion.java @@ -93,6 +93,24 @@ public static void createTableIfNeeded(String schemaName, IDatabaseAdapter targe } } + /** + * Drop the WHOLE_SCHEMA_VERSION table if it exists in the given schema + * @param schemaName + * @param target + */ + public static void dropTable(String schemaName, IDatabaseAdapter target) { + PhysicalDataModel dataModel = new PhysicalDataModel(); + + Table t = buildTableDef(dataModel, schemaName, false); + + // apply this data model to the target if necessary - note - this table + // is not managed in the VERSION_HISTORY table so we need to perform this + // manually. + if (t.exists(target)) { + dataModel.drop(target); + } + } + /** * Grant the user privileges so that the row from this table can * be read by the $healthcheck custom operation. diff --git a/fhir-persistence-schema/src/main/java/com/ibm/fhir/schema/app/Main.java b/fhir-persistence-schema/src/main/java/com/ibm/fhir/schema/app/Main.java index d554ae6d5d1..bb6a97006c0 100644 --- a/fhir-persistence-schema/src/main/java/com/ibm/fhir/schema/app/Main.java +++ b/fhir-persistence-schema/src/main/java/com/ibm/fhir/schema/app/Main.java @@ -50,6 +50,7 @@ import com.ibm.fhir.database.utils.common.JdbcConnectionProvider; import com.ibm.fhir.database.utils.common.JdbcPropertyAdapter; import com.ibm.fhir.database.utils.common.JdbcTarget; +import com.ibm.fhir.database.utils.common.SchemaInfoObject; import com.ibm.fhir.database.utils.db2.Db2Adapter; import com.ibm.fhir.database.utils.db2.Db2GetTenantVariable; import com.ibm.fhir.database.utils.db2.Db2SetTenantVariable; @@ -679,25 +680,41 @@ protected void dropSchema() { JdbcTarget target = new JdbcTarget(c); IDatabaseAdapter adapter = getDbAdapter(dbType, target); - if (dropFhirSchema || dropOauthSchema || dropJavaBatchSchema) { + if (dropFhirSchema) { // Just drop the objects associated with the FHIRDATA schema group pdm.drop(adapter, FhirSchemaGenerator.SCHEMA_GROUP_TAG, FhirSchemaGenerator.FHIRDATA_GROUP); + CreateWholeSchemaVersion.dropTable(schema.getSchemaName(), adapter); + if (!checkSchemaIsEmpty(adapter, schema.getSchemaName())) { + throw new DataAccessException("Schema '" + schema.getSchemaName() + "' not empty after drop"); + } } if (dropOauthSchema) { // Just drop the objects associated with the OAUTH schema group pdm.drop(adapter, FhirSchemaGenerator.SCHEMA_GROUP_TAG, OAuthSchemaGenerator.OAUTH_GROUP); + CreateWholeSchemaVersion.dropTable(schema.getOauthSchemaName(), adapter); + if (!checkSchemaIsEmpty(adapter, schema.getOauthSchemaName())) { + throw new DataAccessException("Schema '" + schema.getOauthSchemaName() + "' not empty after drop"); + } } if (dropJavaBatchSchema) { // Just drop the objects associated with the BATCH schema group pdm.drop(adapter, FhirSchemaGenerator.SCHEMA_GROUP_TAG, JavaBatchSchemaGenerator.BATCH_GROUP); + CreateWholeSchemaVersion.dropTable(schema.getJavaBatchSchemaName(), adapter); + if (!checkSchemaIsEmpty(adapter, schema.getJavaBatchSchemaName())) { + throw new DataAccessException("Schema '" + schema.getJavaBatchSchemaName() + "' not empty after drop"); + } } if (dropAdmin) { // Just drop the objects associated with the ADMIN schema group CreateVersionHistory.generateTable(pdm, ADMIN_SCHEMANAME, true); + CreateControl.buildTableDef(pdm, ADMIN_SCHEMANAME, true); pdm.drop(adapter, FhirSchemaGenerator.SCHEMA_GROUP_TAG, FhirSchemaGenerator.ADMIN_GROUP); + if (!checkSchemaIsEmpty(adapter, schema.getAdminSchemaName())) { + throw new DataAccessException("Schema '" + schema.getAdminSchemaName() + "' not empty after drop"); + } } } catch (Exception x) { c.rollback(); @@ -2381,6 +2398,22 @@ private void runSingleTable(PostgresAdapter adapter, PhysicalDataModel pdm, Stri } } + /** + * Check to see if we have anything left over in a schema we expect to be empty + * @param adapter + * @param schemaName + * @return + */ + private boolean checkSchemaIsEmpty(IDatabaseAdapter adapter, String schemaName) { + List schemaObjects = adapter.listSchemaObjects(schemaName); + boolean result = schemaObjects.isEmpty(); + if (!result) { + // When called, we expect the schema to be empty, so let's dump what we have + final String remaining = schemaObjects.stream().map(Object::toString).collect(Collectors.joining(",")); + logger.warning("Remaining objects in schema '" + schemaName + "': [" + remaining + "]"); + } + return result; + } /** * Process the requested operation */ diff --git a/fhir-persistence-schema/src/main/java/com/ibm/fhir/schema/derby/DerbyFhirDatabase.java b/fhir-persistence-schema/src/main/java/com/ibm/fhir/schema/derby/DerbyFhirDatabase.java index e6bc395ef78..5ac54b90cf9 100644 --- a/fhir-persistence-schema/src/main/java/com/ibm/fhir/schema/derby/DerbyFhirDatabase.java +++ b/fhir-persistence-schema/src/main/java/com/ibm/fhir/schema/derby/DerbyFhirDatabase.java @@ -113,6 +113,14 @@ public DerbyFhirDatabase(String dbPath, Set resourceTypeNames) throws S populateResourceTypeAndParameterNameTableEntries(); } + /** + * Get the FHIR data schema name + * @return + */ + public String getSchemaName() { + return SCHEMA_NAME; + } + /** * prepopulates the bootstrapped derby database with static lookup data. * diff --git a/fhir-persistence-schema/src/test/java/com/ibm/fhir/schema/derby/DerbyFhirDatabaseTest.java b/fhir-persistence-schema/src/test/java/com/ibm/fhir/schema/derby/DerbyFhirDatabaseTest.java index 4df0d773a3f..31ffbd5ce6a 100644 --- a/fhir-persistence-schema/src/test/java/com/ibm/fhir/schema/derby/DerbyFhirDatabaseTest.java +++ b/fhir-persistence-schema/src/test/java/com/ibm/fhir/schema/derby/DerbyFhirDatabaseTest.java @@ -6,6 +6,7 @@ package com.ibm.fhir.schema.derby; +import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; @@ -37,14 +38,14 @@ public void testFhirSchema() throws Exception { DerbyMaster.dropDatabase(DB_NAME); try (DerbyFhirDatabase db = new DerbyFhirDatabase(DB_NAME)) { System.out.println("FHIR database created successfully."); - checkDatabase(db); + checkDatabase(db, db.getSchemaName()); testMigrationFunction(db); } // Now that we've got an existing database, let's try the creation again...which should be a NOP try (DerbyFhirDatabase db = new DerbyFhirDatabase(DB_NAME)) { System.out.println("FHIR database exists."); - checkDatabase(db); + checkDatabase(db, db.getSchemaName()); } } @@ -63,13 +64,22 @@ protected void testMigrationFunction(IConnectionProvider cp) throws SQLException } } - protected void checkDatabase(IConnectionProvider cp) throws SQLException { + /** + * Check the FHIR database schema has been set up correctly + * @param cp + * @throws SQLException + */ + protected void checkDatabase(IConnectionProvider cp, String schemaName) throws SQLException { try (Connection c = cp.getConnection()) { try { JdbcTarget tgt = new JdbcTarget(c); DerbyAdapter adapter = new DerbyAdapter(tgt); checkRefSequence(adapter); + + // Check that we have the correct number of tables. This will need to be updated + // whenever tables are added or removed + assertEquals(adapter.listSchemaObjects(schemaName).size(), 1792); c.commit(); } catch (Throwable t) { c.rollback(); From 925550109f2a5b2e64deb409b44d1aa508a7fb75 Mon Sep 17 00:00:00 2001 From: Robin Arnold Date: Tue, 2 Nov 2021 12:08:57 +0000 Subject: [PATCH 2/5] issue 2751 fixed drop checks for Postgres, Db2 and Derby Signed-off-by: Robin Arnold --- .../database/utils/api/IDatabaseAdapter.java | 1 - .../utils/common/SchemaInfoObject.java | 3 +- .../fhir/database/utils/db2/Db2Adapter.java | 7 ++ .../utils/db2/Db2ListSequencesForSchema.java | 57 +++++++++++++++ .../utils/db2/Db2ListTablesForSchema.java | 2 +- .../utils/db2/Db2ListViewsForSchema.java | 57 +++++++++++++++ .../database/utils/derby/DerbyAdapter.java | 11 ++- .../derby/DerbyListSequencesForSchema.java | 62 ++++++++++++++++ .../utils/derby/DerbyListTablesForSchema.java | 2 +- .../utils/derby/DerbyListViewsForSchema.java | 64 ++++++++++++++++ .../utils/postgres/PostgresAdapter.java | 73 ++++++++++++++++++- ...PostgresDoesForeignKeyConstraintExist.java | 62 ++++++++++++++++ .../postgres/PostgresDoesIndexExist.java | 65 +++++++++++++++++ .../postgres/PostgresDoesTableExist.java | 4 +- .../utils/postgres/PostgresDoesViewExist.java | 65 +++++++++++++++++ .../PostgresListSequencesForSchema.java | 60 +++++++++++++++ .../postgres/PostgresListTablesForSchema.java | 2 +- .../postgres/PostgresListViewsForSchema.java | 60 +++++++++++++++ .../schema/derby/DerbyFhirDatabaseTest.java | 4 +- 19 files changed, 648 insertions(+), 13 deletions(-) create mode 100644 fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/db2/Db2ListSequencesForSchema.java create mode 100644 fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/db2/Db2ListViewsForSchema.java create mode 100644 fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyListSequencesForSchema.java create mode 100644 fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyListViewsForSchema.java create mode 100644 fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresDoesForeignKeyConstraintExist.java create mode 100644 fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresDoesIndexExist.java create mode 100644 fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresDoesViewExist.java create mode 100644 fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresListSequencesForSchema.java create mode 100644 fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresListViewsForSchema.java diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/api/IDatabaseAdapter.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/api/IDatabaseAdapter.java index 7f0cd999168..0d2e74c5261 100644 --- a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/api/IDatabaseAdapter.java +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/api/IDatabaseAdapter.java @@ -447,7 +447,6 @@ public void grantSequencePrivileges(String schemaName, String objectName, Collec */ public boolean doesTableExist(String schemaName, String objectName); - /** * Create a database schema * diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/common/SchemaInfoObject.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/common/SchemaInfoObject.java index 9be9bb6e1ad..36c7058e570 100644 --- a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/common/SchemaInfoObject.java +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/common/SchemaInfoObject.java @@ -12,9 +12,10 @@ */ public class SchemaInfoObject { public static enum Type { - TABLE, INDEX, PROCEDURE, + SEQUENCE, + TABLE, VIEW } diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/db2/Db2Adapter.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/db2/Db2Adapter.java index 57780cccd2d..8f5d927c783 100644 --- a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/db2/Db2Adapter.java +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/db2/Db2Adapter.java @@ -646,6 +646,13 @@ public List listSchemaObjects(String schemaName) { List result = new ArrayList<>(); Db2ListTablesForSchema listTables = new Db2ListTablesForSchema(schemaName); result.addAll(runStatement(listTables)); + + Db2ListViewsForSchema listViews = new Db2ListViewsForSchema(schemaName); + result.addAll(runStatement(listViews)); + + Db2ListSequencesForSchema listSequences = new Db2ListSequencesForSchema(schemaName); + result.addAll(runStatement(listSequences)); + return result; } } \ No newline at end of file diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/db2/Db2ListSequencesForSchema.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/db2/Db2ListSequencesForSchema.java new file mode 100644 index 00000000000..a9ff4542017 --- /dev/null +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/db2/Db2ListSequencesForSchema.java @@ -0,0 +1,57 @@ +/* + * (C) Copyright IBM Corp. 2021 + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.ibm.fhir.database.utils.db2; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import com.ibm.fhir.database.utils.api.IDatabaseSupplier; +import com.ibm.fhir.database.utils.api.IDatabaseTranslator; +import com.ibm.fhir.database.utils.common.DataDefinitionUtil; +import com.ibm.fhir.database.utils.common.SchemaInfoObject; +import com.ibm.fhir.database.utils.common.SchemaInfoObject.Type; + +/** + * DAO to fetch the names of sequences in the given schema + */ +public class Db2ListSequencesForSchema implements IDatabaseSupplier> { + + // The schema of the table + private final String schemaName; + + /** + * Public constructor + * @param schemaName + */ + public Db2ListSequencesForSchema(String schemaName) { + this.schemaName = DataDefinitionUtil.assertValidName(schemaName); + } + + @Override + public List run(IDatabaseTranslator translator, Connection c) { + List result = new ArrayList<>(); + // Grab the list of sequences for the configured schema from the DB2 catalog + final String sql = "SELECT seqname FROM SYSCAT.SEQUENCES WHERE seqschema = ?"; + + try (PreparedStatement ps = c.prepareStatement(sql)) { + ps.setString(1, schemaName); + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + result.add(new SchemaInfoObject(Type.SEQUENCE, rs.getString(1))); + } + } + catch (SQLException x) { + throw translator.translate(x); + } + + return result; + } +} diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/db2/Db2ListTablesForSchema.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/db2/Db2ListTablesForSchema.java index 1b6e3c996bf..046472eafef 100644 --- a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/db2/Db2ListTablesForSchema.java +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/db2/Db2ListTablesForSchema.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2019, 2020 + * (C) Copyright IBM Corp. 2021 * * SPDX-License-Identifier: Apache-2.0 */ diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/db2/Db2ListViewsForSchema.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/db2/Db2ListViewsForSchema.java new file mode 100644 index 00000000000..0a5cb032820 --- /dev/null +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/db2/Db2ListViewsForSchema.java @@ -0,0 +1,57 @@ +/* + * (C) Copyright IBM Corp. 2021 + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.ibm.fhir.database.utils.db2; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import com.ibm.fhir.database.utils.api.IDatabaseSupplier; +import com.ibm.fhir.database.utils.api.IDatabaseTranslator; +import com.ibm.fhir.database.utils.common.DataDefinitionUtil; +import com.ibm.fhir.database.utils.common.SchemaInfoObject; +import com.ibm.fhir.database.utils.common.SchemaInfoObject.Type; + +/** + * DAO to fetch the names of views in the given schema + */ +public class Db2ListViewsForSchema implements IDatabaseSupplier> { + + // The schema of the table + private final String schemaName; + + /** + * Public constructor + * @param schemaName + */ + public Db2ListViewsForSchema(String schemaName) { + this.schemaName = DataDefinitionUtil.assertValidName(schemaName); + } + + @Override + public List run(IDatabaseTranslator translator, Connection c) { + List result = new ArrayList<>(); + // Grab the list of views for the configured schema from the DB2 catalog + final String sql = "SELECT viewname FROM SYSCAT.VIEWS WHERE viewschema = ?"; + + try (PreparedStatement ps = c.prepareStatement(sql)) { + ps.setString(1, schemaName); + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + result.add(new SchemaInfoObject(Type.VIEW, rs.getString(1))); + } + } + catch (SQLException x) { + throw translator.translate(x); + } + + return result; + } +} diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyAdapter.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyAdapter.java index 7de63722e3b..9851062a743 100644 --- a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyAdapter.java +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyAdapter.java @@ -373,8 +373,15 @@ public void createOrReplaceView(String schemaName, String viewName, String selec @Override public List listSchemaObjects(String schemaName) { List result = new ArrayList<>(); - DerbyListTablesForSchema dao = new DerbyListTablesForSchema(schemaName); - result.addAll(runStatement(dao)); + DerbyListTablesForSchema listTables = new DerbyListTablesForSchema(schemaName); + result.addAll(runStatement(listTables)); + + DerbyListViewsForSchema listViews = new DerbyListViewsForSchema(schemaName); + result.addAll(runStatement(listViews)); + + DerbyListSequencesForSchema listSequences = new DerbyListSequencesForSchema(schemaName); + result.addAll(runStatement(listSequences)); + return result; } } \ No newline at end of file diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyListSequencesForSchema.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyListSequencesForSchema.java new file mode 100644 index 00000000000..946c994511e --- /dev/null +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyListSequencesForSchema.java @@ -0,0 +1,62 @@ +/* + * (C) Copyright IBM Corp. 2021 + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.ibm.fhir.database.utils.derby; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import com.ibm.fhir.database.utils.api.IDatabaseSupplier; +import com.ibm.fhir.database.utils.api.IDatabaseTranslator; +import com.ibm.fhir.database.utils.common.DataDefinitionUtil; +import com.ibm.fhir.database.utils.common.SchemaInfoObject; +import com.ibm.fhir.database.utils.common.SchemaInfoObject.Type; + +/** + * DAO to fetch the names of sequences in the given schema + */ +public class DerbyListSequencesForSchema implements IDatabaseSupplier> { + + // The schema of the table + private final String schemaName; + + /** + * Public constructor + * @param schemaName + */ + public DerbyListSequencesForSchema(String schemaName) { + this.schemaName = DataDefinitionUtil.assertValidName(schemaName); + } + + @Override + public List run(IDatabaseTranslator translator, Connection c) { + List result = new ArrayList<>(); + // Grab the list of sequences for the configured schema from the Derby sys catalog + final String sql = "" + + "SELECT seqs.sequencename FROM sys.syssequences AS seqs " + + " JOIN sys.sysschemas AS schemas " + + " ON (seqs.schemaid = schemas.schemaid) " + + " WHERE schemas.schemaname = ?" + ; + + try (PreparedStatement ps = c.prepareStatement(sql)) { + ps.setString(1, schemaName); + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + result.add(new SchemaInfoObject(Type.SEQUENCE, rs.getString(1))); + } + } + catch (SQLException x) { + throw translator.translate(x); + } + + return result; + } +} \ No newline at end of file diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyListTablesForSchema.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyListTablesForSchema.java index b1fd7aca6f4..65f10f521a8 100644 --- a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyListTablesForSchema.java +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyListTablesForSchema.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2019, 2020 + * (C) Copyright IBM Corp. 2021 * * SPDX-License-Identifier: Apache-2.0 */ diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyListViewsForSchema.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyListViewsForSchema.java new file mode 100644 index 00000000000..95b5c61d77c --- /dev/null +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyListViewsForSchema.java @@ -0,0 +1,64 @@ +/* + * (C) Copyright IBM Corp. 2021 + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.ibm.fhir.database.utils.derby; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import com.ibm.fhir.database.utils.api.IDatabaseSupplier; +import com.ibm.fhir.database.utils.api.IDatabaseTranslator; +import com.ibm.fhir.database.utils.common.DataDefinitionUtil; +import com.ibm.fhir.database.utils.common.SchemaInfoObject; +import com.ibm.fhir.database.utils.common.SchemaInfoObject.Type; + +/** + * DAO to fetch the names of views in the given schema + */ +public class DerbyListViewsForSchema implements IDatabaseSupplier> { + + // The schema of the table + private final String schemaName; + + /** + * Public constructor + * @param schemaName + */ + public DerbyListViewsForSchema(String schemaName) { + this.schemaName = DataDefinitionUtil.assertValidName(schemaName); + } + + @Override + public List run(IDatabaseTranslator translator, Connection c) { + List result = new ArrayList<>(); + // Grab the list of views for the configured schema from the Derby sys catalog + final String sql = "" + + "SELECT tables.tablename FROM sys.systables AS tables " + + " JOIN sys.sysschemas AS schemas " + + " ON (tables.schemaid = schemas.schemaid) " + + " JOIN sys.sysviews AS views " + + " ON (views.tableid = tables.tableid) " + + " WHERE schemas.schemaname = ?" + ; + + try (PreparedStatement ps = c.prepareStatement(sql)) { + ps.setString(1, schemaName); + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + result.add(new SchemaInfoObject(Type.VIEW, rs.getString(1))); + } + } + catch (SQLException x) { + throw translator.translate(x); + } + + return result; + } +} \ No newline at end of file diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresAdapter.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresAdapter.java index 484a7d3cc45..14442c223a5 100644 --- a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresAdapter.java +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresAdapter.java @@ -28,6 +28,7 @@ import com.ibm.fhir.database.utils.common.AddForeignKeyConstraint; import com.ibm.fhir.database.utils.common.CommonDatabaseAdapter; import com.ibm.fhir.database.utils.common.DataDefinitionUtil; +import com.ibm.fhir.database.utils.common.DropForeignKeyConstraint; import com.ibm.fhir.database.utils.common.SchemaInfoObject; import com.ibm.fhir.database.utils.model.CheckConstraint; import com.ibm.fhir.database.utils.model.ColumnBase; @@ -38,6 +39,7 @@ import com.ibm.fhir.database.utils.model.Privilege; import com.ibm.fhir.database.utils.model.Table; import com.ibm.fhir.database.utils.model.With; +import com.ibm.fhir.database.utils.tenant.DropViewDAO; /** * A PostgreSql database target @@ -194,6 +196,28 @@ public boolean doesTableExist(String schemaName, String tableName) { return runStatement(dao); } + /** + * Check if the named view currently exists + * @param schemaName + * @param viewName + * @return + */ + private boolean doesViewExist(String schemaName, String viewName) { + PostgresDoesViewExist dao = new PostgresDoesViewExist(schemaName, viewName); + return runStatement(dao); + } + + /** + * Check if the named index currently exists + * @param schemaName + * @param indexName + * @return + */ + private boolean doesIndexExist(String schemaName, String indexName) { + PostgresDoesIndexExist dao = new PostgresDoesIndexExist(schemaName, indexName); + return runStatement(dao); + } + @Override public void createSequence(String schemaName, String sequenceName, long startWith, int cache, int incrementBy) { /* CREATE SEQUENCE fhir_sequence @@ -459,8 +483,53 @@ public void createOrReplaceFunction(String schemaName, String functionName, Supp @Override public List listSchemaObjects(String schemaName) { List result = new ArrayList<>(); - PostgresListTablesForSchema dao = new PostgresListTablesForSchema(schemaName); - result.addAll(runStatement(dao)); + PostgresListTablesForSchema listTables = new PostgresListTablesForSchema(schemaName); + result.addAll(runStatement(listTables)); + + PostgresListViewsForSchema listViews = new PostgresListViewsForSchema(schemaName); + result.addAll(runStatement(listViews)); + + PostgresListSequencesForSchema listSequences = new PostgresListSequencesForSchema(schemaName); + result.addAll(runStatement(listSequences)); + return result; } + + @Override + public void dropForeignKey(String schemaName, String tableName, String constraintName) { + // For PostgreSQL, we need to make sure the constraint exists before + // we drop it, because exceptions break the transaction + PostgresDoesForeignKeyConstraintExist fkExists = new PostgresDoesForeignKeyConstraintExist(schemaName, constraintName); + if (runStatement(fkExists)) { + super.dropForeignKey(schemaName, tableName, constraintName); + } + } + + @Override + public void dropTable(String schemaName, String tableName) { + + // Check the table exists first, otherwise the exception will + // break the current transaction (PostgreSQL behavior) + if (doesTableExist(schemaName, tableName)) { + super.dropTable(schemaName, tableName); + } + } + + @Override + public void dropView(String schemaName, String viewName) { + // Check the view exists before we try to drop it, otherwise + // the exception will break the current transaction + if (doesViewExist(schemaName, viewName)) { + super.dropView(schemaName, viewName); + } + } + + @Override + public void dropIndex(String schemaName, String indexName) { + // Check the index exists before we try to drop it, otherwise + // the exception will break the current transaction + if (doesIndexExist(schemaName, indexName)) { + super.dropIndex(schemaName, indexName); + } + } } \ No newline at end of file diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresDoesForeignKeyConstraintExist.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresDoesForeignKeyConstraintExist.java new file mode 100644 index 00000000000..7e578671e30 --- /dev/null +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresDoesForeignKeyConstraintExist.java @@ -0,0 +1,62 @@ +/* + * (C) Copyright IBM Corp. 2021 + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.ibm.fhir.database.utils.postgres; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import com.ibm.fhir.database.utils.api.IDatabaseSupplier; +import com.ibm.fhir.database.utils.api.IDatabaseTranslator; +import com.ibm.fhir.database.utils.common.DataDefinitionUtil; + +/** + * DAO to check if a named foreign key (FK) constraint exists + */ +public class PostgresDoesForeignKeyConstraintExist implements IDatabaseSupplier { + + // The schema of the table + private final String schemaName; + + // The name of the foreign key constraint + private final String constraintName; + + /** + * Public constructor + * @param schemaName + */ + public PostgresDoesForeignKeyConstraintExist(String schemaName, String constraintName) { + this.schemaName = DataDefinitionUtil.assertValidName(schemaName); + this.constraintName = DataDefinitionUtil.assertValidName(constraintName); + } + + @Override + public Boolean run(IDatabaseTranslator translator, Connection c) { + Boolean result = Boolean.FALSE; + final String sql = "" + + "SELECT 1 " + + " FROM pg_constraint " + + " WHERE contype = 'f' " + + " AND connamespace = ?::regnamespace " + + " AND conname = ? "; + + try (PreparedStatement ps = c.prepareStatement(sql)) { + ps.setString(1, schemaName); + ps.setString(2, constraintName); + ResultSet rs = ps.executeQuery(); + if(rs.next()) { + result = Boolean.TRUE; + } + } + catch (SQLException x) { + throw translator.translate(x); + } + + return result; + } +} \ No newline at end of file diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresDoesIndexExist.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresDoesIndexExist.java new file mode 100644 index 00000000000..34047d5d738 --- /dev/null +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresDoesIndexExist.java @@ -0,0 +1,65 @@ +/* + * (C) Copyright IBM Corp. 2021 + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.ibm.fhir.database.utils.postgres; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import com.ibm.fhir.database.utils.api.IDatabaseSupplier; +import com.ibm.fhir.database.utils.api.IDatabaseTranslator; +import com.ibm.fhir.database.utils.common.DataDefinitionUtil; + +/** + * Check the catalog to see if the named index exists + */ +public class PostgresDoesIndexExist implements IDatabaseSupplier { + + // The schema of the index + private final String schemaName; + + // The name of the index + private final String indexName; + + /** + * Public constructor + * @param schemaName + * @param tableName + */ + public PostgresDoesIndexExist(String schemaName, String indexName) { + this.schemaName = DataDefinitionUtil.assertValidName(schemaName.toLowerCase()); + this.indexName = DataDefinitionUtil.assertValidName(indexName.toLowerCase()); + } + + @Override + public Boolean run(IDatabaseTranslator translator, Connection c) { + Boolean result = false; + // For PostgreSQL, identifier names are always in lowercase unless they are surround with double quotes. + final String sql = "" + + "SELECT 1 FROM pg_catalog.pg_indexes " + + " WHERE schemaname = ? " + + " AND indexname = ? "; + + try (PreparedStatement ps = c.prepareStatement(sql)) { + ps.setString(1, schemaName); + ps.setString(2, indexName); + ResultSet rs = ps.executeQuery(); + if (rs.next()) { + // if we get a row, we know the table exists + result = true; + } + } + catch (SQLException x) { + if (translator.isConnectionError(x)) { + throw translator.translate(x); + } + } + + return result; + } +} \ No newline at end of file diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresDoesTableExist.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresDoesTableExist.java index aa4683e9ce1..2446382b8d3 100644 --- a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresDoesTableExist.java +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresDoesTableExist.java @@ -46,8 +46,8 @@ public Boolean run(IDatabaseTranslator translator, Connection c) { final String sql = " SELECT 1 " + " FROM information_schema.tables " - + " WHERE LOWER(table_schema) = LOWER('" + schemaName + "') " - + " AND LOWER(table_name) = LOWER('" + tableName + "')"; + + " WHERE table_schema = LOWER('" + schemaName + "') " + + " AND table_name = LOWER('" + tableName + "')"; try (PreparedStatement ps = c.prepareStatement(sql)) { ResultSet rs = ps.executeQuery(); diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresDoesViewExist.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresDoesViewExist.java new file mode 100644 index 00000000000..4bade30f709 --- /dev/null +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresDoesViewExist.java @@ -0,0 +1,65 @@ +/* + * (C) Copyright IBM Corp. 2021 + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.ibm.fhir.database.utils.postgres; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import com.ibm.fhir.database.utils.api.IDatabaseSupplier; +import com.ibm.fhir.database.utils.api.IDatabaseTranslator; +import com.ibm.fhir.database.utils.common.DataDefinitionUtil; + +/** + * Check the catalog to see if the named view exists + */ +public class PostgresDoesViewExist implements IDatabaseSupplier { + + // The schema of the table + private final String schemaName; + + // The name of the view + private final String viewName; + + /** + * Public constructor + * @param schemaName + * @param tableName + */ + public PostgresDoesViewExist(String schemaName, String viewName) { + this.schemaName = DataDefinitionUtil.assertValidName(schemaName.toLowerCase()); + this.viewName = DataDefinitionUtil.assertValidName(viewName.toLowerCase()); + } + + @Override + public Boolean run(IDatabaseTranslator translator, Connection c) { + Boolean result = false; + // For PostgreSQL, identifier names are always in lowercase unless they are surround with double quotes. + final String sql = "" + + "SELECT 1 FROM information_schema.views " + + " WHERE table_schema = ? " + + " AND table_name = ? "; + + try (PreparedStatement ps = c.prepareStatement(sql)) { + ps.setString(1, schemaName); + ps.setString(2, viewName); + ResultSet rs = ps.executeQuery(); + if (rs.next()) { + // if we get a row, we know the table exists + result = true; + } + } + catch (SQLException x) { + if (translator.isConnectionError(x)) { + throw translator.translate(x); + } + } + + return result; + } +} \ No newline at end of file diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresListSequencesForSchema.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresListSequencesForSchema.java new file mode 100644 index 00000000000..486bd3598cf --- /dev/null +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresListSequencesForSchema.java @@ -0,0 +1,60 @@ +/* + * (C) Copyright IBM Corp. 2021 + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.ibm.fhir.database.utils.postgres; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import com.ibm.fhir.database.utils.api.IDatabaseSupplier; +import com.ibm.fhir.database.utils.api.IDatabaseTranslator; +import com.ibm.fhir.database.utils.common.DataDefinitionUtil; +import com.ibm.fhir.database.utils.common.SchemaInfoObject; +import com.ibm.fhir.database.utils.common.SchemaInfoObject.Type; + +/** + * DAO to fetch the names of sequences in the given schema + */ +public class PostgresListSequencesForSchema implements IDatabaseSupplier> { + + // The schema of the table + private final String schemaName; + + /** + * Public constructor + * @param schemaName + */ + public PostgresListSequencesForSchema(String schemaName) { + this.schemaName = DataDefinitionUtil.assertValidName(schemaName); + } + + @Override + public List run(IDatabaseTranslator translator, Connection c) { + List result = new ArrayList<>(); + // Grab the list of sequences for the configured schema from the PostgreSQL schema + // catalog + final String sql = "" + + "SELECT sequence_name FROM information_schema.sequences " + + " WHERE sequence_schema = ?"; + + try (PreparedStatement ps = c.prepareStatement(sql)) { + ps.setString(1, schemaName); + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + result.add(new SchemaInfoObject(Type.SEQUENCE, rs.getString(1))); + } + } + catch (SQLException x) { + throw translator.translate(x); + } + + return result; + } +} \ No newline at end of file diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresListTablesForSchema.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresListTablesForSchema.java index 5cd85c699db..a745097c9fa 100644 --- a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresListTablesForSchema.java +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresListTablesForSchema.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2019, 2020 + * (C) Copyright IBM Corp. 2021 * * SPDX-License-Identifier: Apache-2.0 */ diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresListViewsForSchema.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresListViewsForSchema.java new file mode 100644 index 00000000000..2411fbf7f3e --- /dev/null +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresListViewsForSchema.java @@ -0,0 +1,60 @@ +/* + * (C) Copyright IBM Corp. 2021 + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.ibm.fhir.database.utils.postgres; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import com.ibm.fhir.database.utils.api.IDatabaseSupplier; +import com.ibm.fhir.database.utils.api.IDatabaseTranslator; +import com.ibm.fhir.database.utils.common.DataDefinitionUtil; +import com.ibm.fhir.database.utils.common.SchemaInfoObject; +import com.ibm.fhir.database.utils.common.SchemaInfoObject.Type; + +/** + * DAO to fetch the names of views in the given schema + */ +public class PostgresListViewsForSchema implements IDatabaseSupplier> { + + // The schema of the table + private final String schemaName; + + /** + * Public constructor + * @param schemaName + */ + public PostgresListViewsForSchema(String schemaName) { + this.schemaName = DataDefinitionUtil.assertValidName(schemaName); + } + + @Override + public List run(IDatabaseTranslator translator, Connection c) { + List result = new ArrayList<>(); + // Grab the list of views for the configured schema from the PostgreSQL schema + // catalog + final String sql = "" + + "SELECT table_name FROM information_schema.views " + + " WHERE table_schema = ?"; + + try (PreparedStatement ps = c.prepareStatement(sql)) { + ps.setString(1, schemaName); + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + result.add(new SchemaInfoObject(Type.VIEW, rs.getString(1))); + } + } + catch (SQLException x) { + throw translator.translate(x); + } + + return result; + } +} \ No newline at end of file diff --git a/fhir-persistence-schema/src/test/java/com/ibm/fhir/schema/derby/DerbyFhirDatabaseTest.java b/fhir-persistence-schema/src/test/java/com/ibm/fhir/schema/derby/DerbyFhirDatabaseTest.java index 31ffbd5ce6a..c589a339b92 100644 --- a/fhir-persistence-schema/src/test/java/com/ibm/fhir/schema/derby/DerbyFhirDatabaseTest.java +++ b/fhir-persistence-schema/src/test/java/com/ibm/fhir/schema/derby/DerbyFhirDatabaseTest.java @@ -78,8 +78,8 @@ protected void checkDatabase(IConnectionProvider cp, String schemaName) throws S checkRefSequence(adapter); // Check that we have the correct number of tables. This will need to be updated - // whenever tables are added or removed - assertEquals(adapter.listSchemaObjects(schemaName).size(), 1792); + // whenever tables, views or sequences are added or removed + assertEquals(adapter.listSchemaObjects(schemaName).size(), 1943); c.commit(); } catch (Throwable t) { c.rollback(); From ce61dd75ff1fc95e77b03a040443833e72229dc3 Mon Sep 17 00:00:00 2001 From: Robin Arnold Date: Tue, 2 Nov 2021 17:01:27 +0000 Subject: [PATCH 3/5] issue 2751 idempotent schema drop for Db2, PostgreSQL and Derby Signed-off-by: Robin Arnold --- .../database/utils/derby/DerbyAdapter.java | 12 +++- .../utils/model/AlterSequenceStartWith.java | 2 +- .../utils/version/ClearVersionHistoryDAO.java | 56 +++++++++++++++++ .../utils/version/VersionHistoryService.java | 22 +++++++ .../java/com/ibm/fhir/schema/app/Main.java | 28 ++++++--- .../schema/derby/DerbyFhirDatabaseTest.java | 61 +++++++++++++++++++ 6 files changed, 169 insertions(+), 12 deletions(-) create mode 100644 fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/version/ClearVersionHistoryDAO.java diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyAdapter.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyAdapter.java index 9851062a743..14d6a46395a 100644 --- a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyAdapter.java +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyAdapter.java @@ -44,7 +44,7 @@ public class DerbyAdapter extends CommonDatabaseAdapter { // Different warning messages we track so that we only have to report them once private enum MessageKey { MULTITENANCY, CREATE_VAR, CREATE_PERM, ENABLE_ROW_ACCESS, DISABLE_ROW_ACCESS, PARTITIONING, - ROW_TYPE, ROW_ARR_TYPE, DROP_TYPE, CREATE_PROC, DROP_PROC, TABLESPACE, ALTER_TABLE_SEQ_CACHE + ROW_TYPE, ROW_ARR_TYPE, DROP_TYPE, CREATE_PROC, DROP_PROC, DROP_PERM, TABLESPACE, ALTER_TABLE_SEQ_CACHE } // Just warn once for each unique message key. This cleans up build logs a lot @@ -156,6 +156,11 @@ public void dropProcedure(String schemaName, String procedureName) { warnOnce(MessageKey.DROP_PROC, "Drop procedure not supported in Derby"); } + @Override + public void dropPermission(String schemaName, String permissionName) { + warnOnce(MessageKey.DROP_PERM, "Drop permission not supported in Derby"); + } + @Override public void createTablespace(String tablespaceName) { logger.fine("Create tablespace not supported in Derby"); @@ -209,11 +214,14 @@ public void dropSequence(String schemaName, String sequenceName) { // the "RESTRICT" keyword is mandatory in Derby final String sname = DataDefinitionUtil.getQualifiedName(schemaName, sequenceName); final String ddl = "DROP SEQUENCE " + sname + " RESTRICT"; - + try { runStatement(ddl); } catch (UndefinedNameException x) { logger.warning(ddl + "; Sequence not found"); + } catch (Exception sx) { + logger.warning("Drop sequence failed - DDL: " + ddl); + throw sx; } } diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/model/AlterSequenceStartWith.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/model/AlterSequenceStartWith.java index 689f16356fe..b31ee17a5c8 100644 --- a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/model/AlterSequenceStartWith.java +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/model/AlterSequenceStartWith.java @@ -50,7 +50,7 @@ public void apply(Integer priorVersion, IDatabaseAdapter target) { @Override public void drop(IDatabaseAdapter target) { - target.dropSequence(getSchemaName(), getObjectName()); + // NOP. Sequence will be dropped by the object initially creating it } @Override diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/version/ClearVersionHistoryDAO.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/version/ClearVersionHistoryDAO.java new file mode 100644 index 00000000000..e65a09556ee --- /dev/null +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/version/ClearVersionHistoryDAO.java @@ -0,0 +1,56 @@ +/* + * (C) Copyright IBM Corp. 2021 + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.ibm.fhir.database.utils.version; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.ibm.fhir.database.utils.api.IDatabaseStatement; +import com.ibm.fhir.database.utils.api.IDatabaseTranslator; +import com.ibm.fhir.database.utils.common.DataDefinitionUtil; + +/** + * Clear all version history records for a particular schema + * (used when dropping a schema). + */ +public class ClearVersionHistoryDAO implements IDatabaseStatement { + private static final Logger logger = Logger.getLogger(ClearVersionHistoryDAO.class.getName()); + + // The admin schema holding the history table + private final String adminSchemaName; + + // The schema we wish to clear + private final String schemaName; + + /** + * Public constructor + * + * @param adminSchemaName + * @param schemaName + */ + public ClearVersionHistoryDAO(String adminSchemaName, String schemaName) { + this.adminSchemaName = adminSchemaName; + this.schemaName = schemaName; + } + + @Override + public void run(IDatabaseTranslator translator, Connection c) { + final String tbl = DataDefinitionUtil.getQualifiedName(adminSchemaName, SchemaConstants.VERSION_HISTORY); + final String dml = "DELETE FROM " + tbl + " WHERE " + SchemaConstants.SCHEMA_NAME + " = ?"; + try (PreparedStatement ps = c.prepareStatement(dml)) { + ps.setString(1, schemaName); + ps.executeUpdate(); + } + catch (SQLException x) { + logger.log(Level.SEVERE, dml, x); + throw translator.translate(x); + } + } +} diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/version/VersionHistoryService.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/version/VersionHistoryService.java index 8fbf3d3bcb6..e6fa0855728 100644 --- a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/version/VersionHistoryService.java +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/version/VersionHistoryService.java @@ -129,6 +129,28 @@ public void insertVersionHistoryInTx(String objectSchema, String objectType, Str AddVersionDAO dao = new AddVersionDAO(adminSchemaName, objectSchema, objectType, objectName, version); target.runStatement(dao); } + + /** + * Remove the version history information for all objects in the given schema + * @param objectSchema + */ + public void clearVersionHistory(String objectSchema) { + try (ITransaction tx = transactionProvider.getTransaction()) { + try { + // Only perform the clear if the table exists, otherwise we + // can safely assume there's no history left + if (target.doesTableExist(adminSchemaName, SchemaConstants.VERSION_HISTORY)) { + ClearVersionHistoryDAO dao = new ClearVersionHistoryDAO(adminSchemaName, objectSchema); + target.runStatement(dao); + } + } catch (DataAccessException x) { + // Something went wrong, so mark the transaction as failed + tx.setRollbackOnly(); + throw x; + } + } + + } /** * Insert all the entries in the versionHistoryMap in a new transaction (useful diff --git a/fhir-persistence-schema/src/main/java/com/ibm/fhir/schema/app/Main.java b/fhir-persistence-schema/src/main/java/com/ibm/fhir/schema/app/Main.java index bb6a97006c0..ad3ef2c3763 100644 --- a/fhir-persistence-schema/src/main/java/com/ibm/fhir/schema/app/Main.java +++ b/fhir-persistence-schema/src/main/java/com/ibm/fhir/schema/app/Main.java @@ -679,32 +679,42 @@ protected void dropSchema() { try { JdbcTarget target = new JdbcTarget(c); IDatabaseAdapter adapter = getDbAdapter(dbType, target); + VersionHistoryService vhs = + new VersionHistoryService(schema.getAdminSchemaName(), schema.getSchemaName(), schema.getOauthSchemaName(), schema.getJavaBatchSchemaName()); + vhs.setTransactionProvider(transactionProvider); + vhs.setTarget(adapter); if (dropFhirSchema) { // Just drop the objects associated with the FHIRDATA schema group + final String schemaName = schema.getSchemaName(); pdm.drop(adapter, FhirSchemaGenerator.SCHEMA_GROUP_TAG, FhirSchemaGenerator.FHIRDATA_GROUP); - CreateWholeSchemaVersion.dropTable(schema.getSchemaName(), adapter); - if (!checkSchemaIsEmpty(adapter, schema.getSchemaName())) { - throw new DataAccessException("Schema '" + schema.getSchemaName() + "' not empty after drop"); + CreateWholeSchemaVersion.dropTable(schemaName, adapter); + if (!checkSchemaIsEmpty(adapter, schemaName)) { + throw new DataAccessException("Schema '" + schemaName + "' not empty after drop"); } + vhs.clearVersionHistory(schemaName); } if (dropOauthSchema) { // Just drop the objects associated with the OAUTH schema group + final String schemaName = schema.getOauthSchemaName(); pdm.drop(adapter, FhirSchemaGenerator.SCHEMA_GROUP_TAG, OAuthSchemaGenerator.OAUTH_GROUP); - CreateWholeSchemaVersion.dropTable(schema.getOauthSchemaName(), adapter); - if (!checkSchemaIsEmpty(adapter, schema.getOauthSchemaName())) { - throw new DataAccessException("Schema '" + schema.getOauthSchemaName() + "' not empty after drop"); + CreateWholeSchemaVersion.dropTable(schemaName, adapter); + if (!checkSchemaIsEmpty(adapter, schemaName)) { + throw new DataAccessException("Schema '" + schemaName + "' not empty after drop"); } + vhs.clearVersionHistory(schemaName); } if (dropJavaBatchSchema) { // Just drop the objects associated with the BATCH schema group + final String schemaName = schema.getJavaBatchSchemaName(); pdm.drop(adapter, FhirSchemaGenerator.SCHEMA_GROUP_TAG, JavaBatchSchemaGenerator.BATCH_GROUP); - CreateWholeSchemaVersion.dropTable(schema.getJavaBatchSchemaName(), adapter); - if (!checkSchemaIsEmpty(adapter, schema.getJavaBatchSchemaName())) { - throw new DataAccessException("Schema '" + schema.getJavaBatchSchemaName() + "' not empty after drop"); + CreateWholeSchemaVersion.dropTable(schemaName, adapter); + if (!checkSchemaIsEmpty(adapter, schemaName)) { + throw new DataAccessException("Schema '" + schemaName + "' not empty after drop"); } + vhs.clearVersionHistory(schemaName); } if (dropAdmin) { diff --git a/fhir-persistence-schema/src/test/java/com/ibm/fhir/schema/derby/DerbyFhirDatabaseTest.java b/fhir-persistence-schema/src/test/java/com/ibm/fhir/schema/derby/DerbyFhirDatabaseTest.java index c589a339b92..5d6449c05c0 100644 --- a/fhir-persistence-schema/src/test/java/com/ibm/fhir/schema/derby/DerbyFhirDatabaseTest.java +++ b/fhir-persistence-schema/src/test/java/com/ibm/fhir/schema/derby/DerbyFhirDatabaseTest.java @@ -9,19 +9,34 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import java.sql.Connection; import java.sql.SQLException; +import java.util.List; +import java.util.stream.Collectors; import org.testng.annotations.Test; import com.ibm.fhir.database.utils.api.IConnectionProvider; +import com.ibm.fhir.database.utils.api.ITransaction; +import com.ibm.fhir.database.utils.api.ITransactionProvider; import com.ibm.fhir.database.utils.common.GetSequenceNextValueDAO; import com.ibm.fhir.database.utils.common.JdbcTarget; +import com.ibm.fhir.database.utils.common.SchemaInfoObject; import com.ibm.fhir.database.utils.derby.DerbyAdapter; import com.ibm.fhir.database.utils.derby.DerbyMaster; +import com.ibm.fhir.database.utils.model.DatabaseObjectType; +import com.ibm.fhir.database.utils.model.PhysicalDataModel; +import com.ibm.fhir.database.utils.pool.PoolConnectionProvider; +import com.ibm.fhir.database.utils.transaction.SimpleTransactionProvider; +import com.ibm.fhir.database.utils.version.CreateVersionHistory; +import com.ibm.fhir.database.utils.version.CreateWholeSchemaVersion; +import com.ibm.fhir.database.utils.version.VersionHistoryService; +import com.ibm.fhir.schema.app.Main; import com.ibm.fhir.schema.control.FhirSchemaConstants; +import com.ibm.fhir.schema.control.FhirSchemaGenerator; import com.ibm.fhir.schema.control.GetXXLogicalResourceNeedsMigration; /** @@ -29,6 +44,7 @@ */ public class DerbyFhirDatabaseTest { private static final String DB_NAME = "target/derby/fhirDB"; + private static final String ADMIN_SCHEMA_NAME = Main.ADMIN_SCHEMANAME; @Test public void testFhirSchema() throws Exception { @@ -46,9 +62,54 @@ public void testFhirSchema() throws Exception { try (DerbyFhirDatabase db = new DerbyFhirDatabase(DB_NAME)) { System.out.println("FHIR database exists."); checkDatabase(db, db.getSchemaName()); + + // Test the schema drop + testDrop(db, db.getSchemaName()); } } + /** + * Test dropping the schema + * @param cp + * @param schemaName + * @throws SQLException + */ + protected void testDrop(IConnectionProvider cp, String schemaName) throws SQLException { + PoolConnectionProvider connectionPool = new PoolConnectionProvider(cp, 10); + ITransactionProvider transactionProvider = new SimpleTransactionProvider(cp); + DerbyAdapter adapter = new DerbyAdapter(connectionPool); + VersionHistoryService vhs = new VersionHistoryService(ADMIN_SCHEMA_NAME, schemaName); + vhs.setTransactionProvider(transactionProvider); + vhs.setTarget(adapter); + + try (ITransaction tx = transactionProvider.getTransaction()) { + PhysicalDataModel pdm = new PhysicalDataModel(); + FhirSchemaGenerator gen = new FhirSchemaGenerator(ADMIN_SCHEMA_NAME, schemaName, false); + gen.buildSchema(pdm); + pdm.drop(adapter, FhirSchemaGenerator.SCHEMA_GROUP_TAG, FhirSchemaGenerator.FHIRDATA_GROUP); + + CreateWholeSchemaVersion.dropTable(schemaName, adapter); + + // Check that the schema is empty + List schemaObjects = adapter.listSchemaObjects(schemaName); + boolean schemaIsEmpty = schemaObjects.isEmpty(); + if (!schemaIsEmpty) { + // When called, we expect the schema to be empty, so let's dump what we have + final String remaining = schemaObjects.stream().map(Object::toString).collect(Collectors.joining(",")); + System.out.println("Remaining objects in schema '" + schemaName + "': [" + remaining + "]"); + } + assertTrue(schemaIsEmpty); + } + + // Now we're all done we can finally clean up the version-history for + // our schema. Make sure we don't get a version for an object we know + // we created + vhs.clearVersionHistory(schemaName); + vhs.init(); + Integer versionCheck = vhs.getVersion(schemaName, DatabaseObjectType.SEQUENCE.name(), FhirSchemaConstants.FHIR_REF_SEQUENCE); + assertEquals((int)versionCheck, 0); + } + protected void testMigrationFunction(IConnectionProvider cp) throws SQLException { try (Connection c = cp.getConnection()) { try { From af373e14ccc405a644a50bfa609f83dd7eb58b3e Mon Sep 17 00:00:00 2001 From: Robin Arnold Date: Wed, 3 Nov 2021 15:18:35 +0000 Subject: [PATCH 4/5] issue 2751 updates per review comments Signed-off-by: Robin Arnold --- .../database/utils/common/ListTablesDAO.java | 16 ---------------- .../database/utils/common/SchemaInfoObject.java | 3 ++- .../fhir/database/utils/derby/DerbyAdapter.java | 2 +- .../utils/postgres/PostgresDoesIndexExist.java | 4 ++-- 4 files changed, 5 insertions(+), 20 deletions(-) delete mode 100644 fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/common/ListTablesDAO.java diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/common/ListTablesDAO.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/common/ListTablesDAO.java deleted file mode 100644 index 0d756e6386c..00000000000 --- a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/common/ListTablesDAO.java +++ /dev/null @@ -1,16 +0,0 @@ -/* - * (C) Copyright IBM Corp. 2021 - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package com.ibm.fhir.database.utils.common; - -import com.ibm.fhir.database.utils.api.IDatabaseSupplier; - -/** - * A DAO to list the tables in a given schema - */ -public interface ListTablesDAO extends IDatabaseSupplier{ - -} diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/common/SchemaInfoObject.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/common/SchemaInfoObject.java index 36c7058e570..200a8934905 100644 --- a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/common/SchemaInfoObject.java +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/common/SchemaInfoObject.java @@ -11,7 +11,8 @@ * A database object (table, index, view etc) existing within a schema */ public class SchemaInfoObject { - public static enum Type { + public enum Type { + FUNCTION, INDEX, PROCEDURE, SEQUENCE, diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyAdapter.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyAdapter.java index 14d6a46395a..2875168acbb 100644 --- a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyAdapter.java +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/derby/DerbyAdapter.java @@ -214,7 +214,7 @@ public void dropSequence(String schemaName, String sequenceName) { // the "RESTRICT" keyword is mandatory in Derby final String sname = DataDefinitionUtil.getQualifiedName(schemaName, sequenceName); final String ddl = "DROP SEQUENCE " + sname + " RESTRICT"; - + try { runStatement(ddl); } catch (UndefinedNameException x) { diff --git a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresDoesIndexExist.java b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresDoesIndexExist.java index 34047d5d738..cd4781ed5f7 100644 --- a/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresDoesIndexExist.java +++ b/fhir-database-utils/src/main/java/com/ibm/fhir/database/utils/postgres/PostgresDoesIndexExist.java @@ -29,7 +29,7 @@ public class PostgresDoesIndexExist implements IDatabaseSupplier { /** * Public constructor * @param schemaName - * @param tableName + * @param indexName */ public PostgresDoesIndexExist(String schemaName, String indexName) { this.schemaName = DataDefinitionUtil.assertValidName(schemaName.toLowerCase()); @@ -50,7 +50,7 @@ public Boolean run(IDatabaseTranslator translator, Connection c) { ps.setString(2, indexName); ResultSet rs = ps.executeQuery(); if (rs.next()) { - // if we get a row, we know the table exists + // if we get a row, we know the index exists result = true; } } From a56438315e4aa74c868864569f1689e71151e1a5 Mon Sep 17 00:00:00 2001 From: Lee Surprenant Date: Thu, 4 Nov 2021 07:36:33 -0400 Subject: [PATCH 5/5] copyright update but really just to force a rebuild... Signed-off-by: Lee Surprenant --- .../main/java/com/ibm/fhir/schema/derby/DerbyFhirDatabase.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fhir-persistence-schema/src/main/java/com/ibm/fhir/schema/derby/DerbyFhirDatabase.java b/fhir-persistence-schema/src/main/java/com/ibm/fhir/schema/derby/DerbyFhirDatabase.java index 5ac54b90cf9..78583215f48 100644 --- a/fhir-persistence-schema/src/main/java/com/ibm/fhir/schema/derby/DerbyFhirDatabase.java +++ b/fhir-persistence-schema/src/main/java/com/ibm/fhir/schema/derby/DerbyFhirDatabase.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2019, 2020 + * (C) Copyright IBM Corp. 2019, 2021 * * SPDX-License-Identifier: Apache-2.0 */