diff --git a/metacat-common-server/src/main/java/com/netflix/metacat/common/server/connectors/exception/ConnectorException.java b/metacat-common-server/src/main/java/com/netflix/metacat/common/server/connectors/exception/ConnectorException.java index ed4125774..d45dec070 100644 --- a/metacat-common-server/src/main/java/com/netflix/metacat/common/server/connectors/exception/ConnectorException.java +++ b/metacat-common-server/src/main/java/com/netflix/metacat/common/server/connectors/exception/ConnectorException.java @@ -50,7 +50,7 @@ public ConnectorException(final String message, @Nullable final Throwable cause) * * @param message message * @param cause cause - * @param enableSuppression eable suppression + * @param enableSuppression enable suppression * @param writableStackTrace stacktrace */ public ConnectorException( diff --git a/metacat-common-server/src/main/java/com/netflix/metacat/common/server/connectors/exception/DatabasePreconditionFailedException.java b/metacat-common-server/src/main/java/com/netflix/metacat/common/server/connectors/exception/DatabasePreconditionFailedException.java new file mode 100644 index 000000000..54b153ffe --- /dev/null +++ b/metacat-common-server/src/main/java/com/netflix/metacat/common/server/connectors/exception/DatabasePreconditionFailedException.java @@ -0,0 +1,48 @@ +package com.netflix.metacat.common.server.connectors.exception; + +/* + * + * Copyright 2024 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +import com.netflix.metacat.common.QualifiedName; +import lombok.Getter; + +import javax.annotation.Nullable; + +/** + * Exception when database can't be deleted because ON DELETE CASCADE is + * disabled and a table still exists in the database. + * + * @author gtret + */ +@Getter +public class DatabasePreconditionFailedException extends ConnectorException { + /** + * Constructor. + * + * @param name qualified name of the database + * @param message error description + * @param error stacktrace + */ + public DatabasePreconditionFailedException(final QualifiedName name, + @Nullable final String message, + @Nullable final Throwable error) { + super(String.format("Precondition failed to update table %s. %s", name, message), error); + } +} + + diff --git a/metacat-connector-polaris/src/functionalTest/resources/schema.sql b/metacat-connector-polaris/src/functionalTest/resources/schema.sql index 9b91312fd..5a4a32738 100644 --- a/metacat-connector-polaris/src/functionalTest/resources/schema.sql +++ b/metacat-connector-polaris/src/functionalTest/resources/schema.sql @@ -25,5 +25,5 @@ create table TBLS ( created_date TIMESTAMP not null, last_updated_by STRING(255), last_updated_date TIMESTAMP not null, - foreign key (db_name) references DBS(name) ON DELETE CASCADE ON UPDATE CASCADE + foreign key (db_name) references DBS(name) ON DELETE RESTRICT ON UPDATE CASCADE ); diff --git a/metacat-connector-polaris/src/main/java/com/netflix/metacat/connector/polaris/PolarisConnectorDatabaseService.java b/metacat-connector-polaris/src/main/java/com/netflix/metacat/connector/polaris/PolarisConnectorDatabaseService.java index 94d67780e..51e4e31ee 100644 --- a/metacat-connector-polaris/src/main/java/com/netflix/metacat/connector/polaris/PolarisConnectorDatabaseService.java +++ b/metacat-connector-polaris/src/main/java/com/netflix/metacat/connector/polaris/PolarisConnectorDatabaseService.java @@ -10,6 +10,7 @@ import com.netflix.metacat.common.server.connectors.exception.ConnectorException; import com.netflix.metacat.common.server.connectors.exception.DatabaseAlreadyExistsException; import com.netflix.metacat.common.server.connectors.exception.DatabaseNotFoundException; +import com.netflix.metacat.common.server.connectors.exception.DatabasePreconditionFailedException; import com.netflix.metacat.common.server.connectors.exception.InvalidMetaException; import com.netflix.metacat.common.server.connectors.model.DatabaseInfo; import com.netflix.metacat.connector.polaris.common.PolarisUtils; @@ -87,6 +88,13 @@ public void delete(final ConnectorRequestContext context, final QualifiedName na try { this.polarisStoreService.deleteDatabase(name.getDatabaseName()); } catch (DataIntegrityViolationException exception) { + if (exception.getCause() instanceof org.hibernate.exception.ConstraintViolationException) { + throw new DatabasePreconditionFailedException( + name, + String.format("Cannot delete database %s because it is not empty.", name.getDatabaseName()), + exception + ); + } throw new InvalidMetaException(name, exception); } catch (Exception exception) { throw new ConnectorException( diff --git a/metacat-connector-polaris/src/test/java/com/netflix/metacat/connector/polaris/PolarisConnectorDatabaseServiceTest.java b/metacat-connector-polaris/src/test/java/com/netflix/metacat/connector/polaris/PolarisConnectorDatabaseServiceTest.java index e297704f8..b765e7735 100644 --- a/metacat-connector-polaris/src/test/java/com/netflix/metacat/connector/polaris/PolarisConnectorDatabaseServiceTest.java +++ b/metacat-connector-polaris/src/test/java/com/netflix/metacat/connector/polaris/PolarisConnectorDatabaseServiceTest.java @@ -1,4 +1,3 @@ - package com.netflix.metacat.connector.polaris; import com.google.common.collect.Maps; @@ -167,4 +166,3 @@ public void testDeleteDb() { Assert.assertFalse(polarisDBService.exists(requestContext, DB1_QUALIFIED_NAME)); } } - diff --git a/metacat-connector-polaris/src/test/java/com/netflix/metacat/connector/polaris/store/PolarisStoreConnectorTest.java b/metacat-connector-polaris/src/test/java/com/netflix/metacat/connector/polaris/store/PolarisStoreConnectorTest.java index d36e92270..fd1f4ab06 100644 --- a/metacat-connector-polaris/src/test/java/com/netflix/metacat/connector/polaris/store/PolarisStoreConnectorTest.java +++ b/metacat-connector-polaris/src/test/java/com/netflix/metacat/connector/polaris/store/PolarisStoreConnectorTest.java @@ -19,6 +19,7 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.dao.DataAccessException; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.data.auditing.AuditingHandler; import org.springframework.data.auditing.DateTimeProvider; @@ -182,6 +183,24 @@ public void testTableCreationAndDeletionWithParams() { Assert.assertFalse(polarisConnector.tableExistsById(tblEntity.getTblId())); } + /** + * Test database deletion if table exists and ON DELETE CASCADE is disabled. + */ + @Test + public void testDbDeletionNoCascade() { + final String dbName = generateDatabaseName(); + final String tblName = generateTableName(); + final PolarisDatabaseEntity dbEntity = createDB(dbName); + final PolarisTableEntity tblEntity = createTable(dbName, tblName); + + Assert.assertTrue(polarisConnector.databaseExists(dbName)); + Assert.assertTrue(polarisConnector.tableExists(dbName, tblName)); + Assertions.assertThrows(DataIntegrityViolationException.class, () -> + polarisConnector.deleteDatabase(dbName)); + Assert.assertTrue(polarisConnector.databaseExists(dbName)); + Assert.assertTrue(polarisConnector.tableExists(dbName, tblName)); + } + /** * Test to verify that table name can be updated. */ diff --git a/metacat-connector-polaris/src/test/resources/h2db/schema.sql b/metacat-connector-polaris/src/test/resources/h2db/schema.sql index 1470b9548..e050e4ee6 100644 --- a/metacat-connector-polaris/src/test/resources/h2db/schema.sql +++ b/metacat-connector-polaris/src/test/resources/h2db/schema.sql @@ -25,7 +25,7 @@ create table TBLS ( created_date TIMESTAMP not null, last_updated_by varchar(255), last_updated_date TIMESTAMP not null, - foreign key (db_name) references DBS(name) ON DELETE CASCADE ON UPDATE CASCADE + foreign key (db_name) references DBS(name) ON DELETE RESTRICT ON UPDATE CASCADE ); CREATE INDEX DB_NAME_IDX ON TBLS(db_name); diff --git a/metacat-functional-tests/metacat-test-cluster/datastores/crdb/sql/schema.sql b/metacat-functional-tests/metacat-test-cluster/datastores/crdb/sql/schema.sql index 9f580ebe0..96d599863 100644 --- a/metacat-functional-tests/metacat-test-cluster/datastores/crdb/sql/schema.sql +++ b/metacat-functional-tests/metacat-test-cluster/datastores/crdb/sql/schema.sql @@ -25,5 +25,5 @@ create table TBLS ( last_updated_by STRING(255), last_updated_date TIMESTAMP not null, constraint uniq_name unique(db_name, tbl_name), - foreign key (db_name) references DBS(name) ON DELETE CASCADE ON UPDATE CASCADE + foreign key (db_name) references DBS(name) ON DELETE RESTRICT ON UPDATE CASCADE );