diff --git a/fda-metadata-db/api/src/main/java/at/tuwien/api/database/table/columns/ColumnCreateDto.java b/fda-metadata-db/api/src/main/java/at/tuwien/api/database/table/columns/ColumnCreateDto.java index 0ad794410e7ced31c1ff1210643134f709ec38f6..502cdcf4ae3b7a39492cbce0a48b5dd52fd1cd09 100644 --- a/fda-metadata-db/api/src/main/java/at/tuwien/api/database/table/columns/ColumnCreateDto.java +++ b/fda-metadata-db/api/src/main/java/at/tuwien/api/database/table/columns/ColumnCreateDto.java @@ -43,16 +43,17 @@ public class ColumnCreateDto { private Boolean unique; @JsonProperty("check_expression") + @Schema(description = "check constraint", example = "id > 0") private String checkExpression; @JsonProperty("foreign_key") private String foreignKey = null; - @Parameter(description = "foreign key reference, only considered when foreignKey != null") + @Schema(description = "foreign key reference, only considered when foreignKey != null") private String references = null; @JsonProperty("enum_values") - @Parameter(description = "enum values, only considered when type = ENUM") + @Schema(description = "enum values, only considered when type = ENUM") private String[] enumValues = null; } diff --git a/fda-table-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java b/fda-table-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java index ecf46f1916d75a7d8d5a062ae75af7264e9e5931..e660a4a933be274645d2960e0adf22547a5dbc61 100644 --- a/fda-table-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java +++ b/fda-table-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java @@ -3,6 +3,8 @@ package at.tuwien; import at.tuwien.api.database.query.QueryBriefDto; import at.tuwien.api.database.query.QueryDto; import at.tuwien.api.database.table.TableCreateDto; +import at.tuwien.api.database.table.columns.ColumnCreateDto; +import at.tuwien.api.database.table.columns.ColumnTypeDto; import at.tuwien.api.user.UserDto; import at.tuwien.entities.container.image.*; import at.tuwien.entities.database.table.columns.concepts.Concept; @@ -324,13 +326,15 @@ public abstract class BaseUnitTest { public final static String COLUMN_4_1_NAME = "id"; public final static String COLUMN_4_1_INTERNAL_NAME = "id"; public final static TableColumnType COLUMN_4_1_TYPE = TableColumnType.NUMBER; + public final static ColumnTypeDto COLUMN_4_1_TYPE_DTO = ColumnTypeDto.NUMBER; public final static Long COLUMN_4_1_DATE_FORMAT = null; public final static Boolean COLUMN_4_1_NULL = false; public final static Boolean COLUMN_4_1_UNIQUE = true; public final static Boolean COLUMN_4_1_AUTO_GENERATED = true; public final static String COLUMN_4_1_FOREIGN_KEY = null; - public final static String COLUMN_4_1_CHECK = null; + public final static String COLUMN_4_1_REFERENCES = null; public final static List<String> COLUMN_4_1_ENUM_VALUES = null; + public final static String[] COLUMN_4_1_ENUM_VALUES_ARRAY = null; public final static Long COLUMN_4_2_ID = 10L; public final static Integer COLUMN_4_2_ORDINALPOS = 1; @@ -338,13 +342,16 @@ public abstract class BaseUnitTest { public final static String COLUMN_4_2_NAME = "Animal Name"; public final static String COLUMN_4_2_INTERNAL_NAME = "animal_name"; public final static TableColumnType COLUMN_4_2_TYPE = TableColumnType.STRING; + public final static ColumnTypeDto COLUMN_4_2_TYPE_DTO = ColumnTypeDto.STRING; public final static Long COLUMN_4_2_DATE_FORMAT = null; public final static Boolean COLUMN_4_2_NULL = true; public final static Boolean COLUMN_4_2_UNIQUE = false; public final static Boolean COLUMN_4_2_AUTO_GENERATED = false; public final static String COLUMN_4_2_FOREIGN_KEY = null; + public final static String COLUMN_4_2_REFERENCES = null; public final static String COLUMN_4_2_CHECK = null; public final static List<String> COLUMN_4_2_ENUM_VALUES = null; + public final static String[] COLUMN_4_2_ENUM_VALUES_ARRAY = null; public final static Long COLUMN_4_3_ID = 11L; public final static Integer COLUMN_4_3_ORDINALPOS = 2; @@ -1486,6 +1493,7 @@ public abstract class BaseUnitTest { .autoGenerated(COLUMN_4_1_AUTO_GENERATED) .isPrimaryKey(COLUMN_4_1_PRIMARY) .enumValues(COLUMN_4_1_ENUM_VALUES) + .references(COLUMN_4_1_REFERENCES) .build(), TableColumn.builder() .id(COLUMN_4_2_ID) @@ -1871,11 +1879,45 @@ public abstract class BaseUnitTest { .build(); public final static TableCreateDto TABLE_3_CREATE_DTO = TableCreateDto.builder() - .name(TABLE_5_NAME) - .description(TABLE_5_DESCRIPTION) + .name(TABLE_3_NAME) + .description(TABLE_3_DESCRIPTION) .columns(List.of()) .build(); + public final static List<ColumnCreateDto> TABLE_4_COLUMNS_INVALID_CREATE = List.of(ColumnCreateDto.builder() + .name(COLUMN_4_2_NAME) + .type(COLUMN_4_2_TYPE_DTO) + .dfid(COLUMN_4_2_DATE_FORMAT) + .nullAllowed(COLUMN_4_2_NULL) + .unique(COLUMN_4_2_UNIQUE) + .primaryKey(COLUMN_4_2_PRIMARY) + .enumValues(COLUMN_4_2_ENUM_VALUES_ARRAY) + .foreignKey("somecolumn") + .references("sometable") + .build()); + + public final static List<ColumnCreateDto> TABLE_4_COLUMNS_CREATE = List.of(ColumnCreateDto.builder() + .name(COLUMN_4_2_NAME) + .type(COLUMN_4_2_TYPE_DTO) + .dfid(COLUMN_4_2_DATE_FORMAT) + .nullAllowed(COLUMN_4_2_NULL) + .unique(COLUMN_4_2_UNIQUE) + .primaryKey(COLUMN_4_2_PRIMARY) + .enumValues(COLUMN_4_2_ENUM_VALUES_ARRAY) + .build()); + + public final static TableCreateDto TABLE_4_CREATE_DTO = TableCreateDto.builder() + .name(TABLE_4_NAME) + .description(TABLE_4_DESCRIPTION) + .columns(TABLE_4_COLUMNS_CREATE) + .build(); + + public final static TableCreateDto TABLE_4_INVALID_CREATE_DTO = TableCreateDto.builder() + .name(TABLE_4_NAME) + .description(TABLE_4_DESCRIPTION) + .columns(TABLE_4_COLUMNS_INVALID_CREATE) + .build(); + public final static Table TABLE_4 = Table.builder() .id(TABLE_4_ID) .created(Instant.now()) diff --git a/fda-table-service/rest-service/src/test/java/at/tuwien/service/TableServiceIntegrationTest.java b/fda-table-service/rest-service/src/test/java/at/tuwien/service/TableServiceIntegrationTest.java index 58a3bbeddce5449777a307cba11c7fa9d62f918c..d031d6994f9c9ff1c36bfc886202782c2af090eb 100644 --- a/fda-table-service/rest-service/src/test/java/at/tuwien/service/TableServiceIntegrationTest.java +++ b/fda-table-service/rest-service/src/test/java/at/tuwien/service/TableServiceIntegrationTest.java @@ -26,6 +26,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import java.io.File; import java.security.Principal; +import java.sql.SQLException; import java.util.Arrays; import java.util.List; @@ -231,6 +232,27 @@ public class TableServiceIntegrationTest extends BaseUnitTest { tableService.createTable(CONTAINER_1_ID, DATABASE_1_ID, TABLE_3_CREATE_DTO, principal); } + @Test + public void create_failedBefore_succeeds() throws UserNotFoundException, TableMalformedException, QueryMalformedException, + DatabaseNotFoundException, ImageNotSupportedException, TableNameExistsException, + ContainerNotFoundException { + final Principal principal = new BasicUserPrincipal(USER_1_USERNAME); + + /* mock */ + when(tableidxRepository.save(any(Table.class))) + .thenReturn(TABLE_1); + when(tableColumnidxRepository.saveAll(anyList())) + .thenReturn(List.of()); + + /* test */ + try { + tableService.createTable(CONTAINER_1_ID, DATABASE_1_ID, TABLE_4_INVALID_CREATE_DTO, principal); + } catch (TableMalformedException e) { + /* ignore */ + } + tableService.createTable(CONTAINER_1_ID, DATABASE_1_ID, TABLE_4_CREATE_DTO, principal); + } + @Test public void delete_succeeds() throws TableMalformedException, QueryMalformedException, DatabaseNotFoundException, ImageNotSupportedException, ContainerNotFoundException, TableNotFoundException, DataProcessingException { diff --git a/fda-table-service/rest-service/src/test/resources/application.properties b/fda-table-service/rest-service/src/test/resources/application.properties index ef996777c5f464998d4109c911a800c99a5ff527..7fb1d2e41f551e322ce5d6eaaabe68ec6d3444e7 100644 --- a/fda-table-service/rest-service/src/test/resources/application.properties +++ b/fda-table-service/rest-service/src/test/resources/application.properties @@ -20,4 +20,4 @@ spring.jpa.properties.hibernate.hbm2ddl.schema_filter_provider=at.tuwien.hiberna # additional logging logging.level.org.hibernate.SQL=debug -logging.level.org.hibernate.type=trace \ No newline at end of file +logging.level.org.hibernate.type=warn \ No newline at end of file diff --git a/fda-table-service/services/src/main/java/at/tuwien/mapper/TableMapper.java b/fda-table-service/services/src/main/java/at/tuwien/mapper/TableMapper.java index c0deddf2f0367e0e893779bbfa8bc79056033ec4..b3e3092660946316b3577e27f992bc6e9e8fb3b8 100644 --- a/fda-table-service/services/src/main/java/at/tuwien/mapper/TableMapper.java +++ b/fda-table-service/services/src/main/java/at/tuwien/mapper/TableMapper.java @@ -297,6 +297,25 @@ public interface TableMapper { } } + default PreparedStatement tableToDropSequenceRawQuery(Connection connection, Database database, TableCreateDto data) + throws ImageNotSupportedException, QueryMalformedException { + if (!database.getContainer().getImage().getRepository().equals("mariadb")) { + log.error("Currently only MariaDB is supported"); + throw new ImageNotSupportedException("Currently only MariaDB is supported"); + } + final StringBuilder statement = new StringBuilder("DROP SEQUENCE `") + .append(tableCreateDtoToSequenceName(data)) + .append("`;"); + try { + final PreparedStatement pstmt = connection.prepareStatement(statement.toString()); + log.trace("prepared drop sequence statement {}", statement); + return pstmt; + } catch (SQLException e) { + log.error("Failed to prepare statement {}, reason: {}", statement, e.getMessage()); + throw new QueryMalformedException("Failed to prepare statement", e); + } + } + default PreparedStatement tableToCreateHistoryViewRawQuery(Connection connection, Table data) throws QueryMalformedException { final StringBuilder statement = new StringBuilder("CREATE VIEW `hs_") .append(data.getInternalName()) diff --git a/fda-table-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java b/fda-table-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java index 88ebcfafbaf34f1556ca273455bd1d08bddd38f8..198e29c8b2ba44df0ced6b1a1d174bd3b9e22ea2 100644 --- a/fda-table-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java +++ b/fda-table-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java @@ -114,8 +114,8 @@ public class TableServiceImpl extends HibernateConnector implements TableService final Optional<Table> optional = tableRepository.findByDatabaseAndInternalName(database, tableMapper.nameToInternalName(createDto.getName())); if (optional.isPresent()) { - log.error("Table name exists"); - throw new TableNameExistsException("Table name exists"); + log.error("Table '{}' exists in metadata database", optional.get().getInternalName()); + throw new TableNameExistsException("Table exists in metadata database"); } /* run query */ final ComboPooledDataSource dataSource = getDataSource(database.getContainer().getImage(), database.getContainer(), database); @@ -132,7 +132,15 @@ public class TableServiceImpl extends HibernateConnector implements TableService final PreparedStatement preparedStatement11 = query.getPreparedStatement(); preparedStatement11.executeUpdate(); } catch (SQLException e) { - log.error("failed to create table, reason: {}", e.getMessage()); + try { + final Connection connection = dataSource.getConnection(); + final PreparedStatement preparedStatement11 = tableMapper.tableToDropSequenceRawQuery(connection, database, createDto); + preparedStatement11.executeUpdate(); + log.debug("successfully rolled back creation of id sequence"); + } catch (SQLException ex) { + log.error("Failed to rollback creation of id sequence"); + } + log.error("Failed to create table, reason: {}", e.getMessage()); throw new TableMalformedException("Failed to create table", e); } finally { dataSource.close();