From eeba6b6b96f7eab60b9de40d7787ce5ebdbd8f0c Mon Sep 17 00:00:00 2001 From: Martin Weise <martin.weise@tuwien.ac.at> Date: Mon, 7 Oct 2024 17:01:09 +0200 Subject: [PATCH] Fixed the UI --- .../at/tuwien/endpoints/TableEndpoint.java | 8 +- dbrepo-metadata-db/1_setup-schema.sql | 92 ++++++++++- dbrepo-metadata-service/api/pom.xml | 9 +- .../api/container/image/DataTypeDto.java | 66 ++++++++ .../tuwien/api/container/image/ImageDto.java | 6 + .../container/image/ContainerImage.java | 4 + .../entities/container/image/DataType.java | 73 +++++++++ .../java/at/tuwien/mapper/MetadataMapper.java | 10 ++ .../at/tuwien/endpoints/DatabaseEndpoint.java | 1 + .../at/tuwien/endpoints/TableEndpoint.java | 9 +- .../src/main/resources/datatypes.json | 15 ++ .../tuwien/service/impl/TableServiceImpl.java | 8 +- dbrepo-ui/components/subset/Builder.vue | 8 +- dbrepo-ui/components/table/TableSchema.vue | 153 +++++++----------- dbrepo-ui/composables/query-service.ts | 61 ++----- dbrepo-ui/dto/mysql.ts | 19 ++- dbrepo-ui/locales/en-US.json | 7 +- dbrepo-ui/nuxt.config.ts | 2 +- .../[database_id]/table/[table_id]/schema.vue | 18 ++- 19 files changed, 402 insertions(+), 167 deletions(-) create mode 100644 dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/image/DataTypeDto.java create mode 100644 dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/container/image/DataType.java create mode 100644 dbrepo-metadata-service/rest-service/src/main/resources/datatypes.json diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java index 674ed4ec4c..548558cafd 100644 --- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java +++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java @@ -12,6 +12,7 @@ import at.tuwien.api.database.table.internal.TableCreateDto; import at.tuwien.api.error.ApiErrorDto; import at.tuwien.exception.*; import at.tuwien.gateway.MetadataServiceGateway; +import at.tuwien.service.SchemaService; import at.tuwien.service.TableService; import at.tuwien.utils.UserUtil; import at.tuwien.validation.EndpointValidator; @@ -49,13 +50,15 @@ import java.util.List; public class TableEndpoint { private final TableService tableService; + private final SchemaService schemaService; private final EndpointValidator endpointValidator; private final MetadataServiceGateway metadataServiceGateway; @Autowired - public TableEndpoint(TableService tableService, EndpointValidator endpointValidator, + public TableEndpoint(TableService tableService, SchemaService schemaService, EndpointValidator endpointValidator, MetadataServiceGateway metadataServiceGateway) { this.tableService = tableService; + this.schemaService = schemaService; this.endpointValidator = endpointValidator; this.metadataServiceGateway = metadataServiceGateway; } @@ -105,8 +108,9 @@ public class TableEndpoint { /* create */ final PrivilegedDatabaseDto database = metadataServiceGateway.getDatabaseById(databaseId); try { + final TableDto table = tableService.createTable(database, data); return ResponseEntity.status(HttpStatus.CREATED) - .body(tableService.createTable(database, data)); + .body(schemaService.inspectTable(database, table.getInternalName())); } catch (SQLException e) { log.error("Failed to establish connection to database: {}", e.getMessage()); throw new DatabaseUnavailableException("Failed to establish connection to database: " + e.getMessage(), e); diff --git a/dbrepo-metadata-db/1_setup-schema.sql b/dbrepo-metadata-db/1_setup-schema.sql index 7e5caf6b6f..db90e94c1f 100644 --- a/dbrepo-metadata-db/1_setup-schema.sql +++ b/dbrepo-metadata-db/1_setup-schema.sql @@ -510,7 +510,33 @@ CREATE TABLE IF NOT EXISTS `mdb_have_access` FOREIGN KEY (user_id) REFERENCES mdb_users (id) ) WITH SYSTEM VERSIONING; +CREATE TABLE IF NOT EXISTS `mdb_image_types` +( + id SERIAL, + image_id BIGINT UNSIGNED NOT NULL, + display_name varchar(255) NOT NULL, + value varchar(255) NOT NULL, + size_min INT UNSIGNED, + size_max INT UNSIGNED, + size_default INT UNSIGNED, + size_required BOOLEAN COMMENT 'When setting NULL, the service assumes the data type has no size', + size_step INT UNSIGNED, + d_min INT UNSIGNED, + d_max INT UNSIGNED, + d_default INT UNSIGNED, + d_required BOOLEAN COMMENT 'When setting NULL, the service assumes the data type has no d', + d_step INT UNSIGNED, + hint TEXT, + documentation TEXT NOT NULL, + is_quoted BOOLEAN NOT NULL, + is_buildable BOOLEAN NOT NULL, + PRIMARY KEY (id), + FOREIGN KEY (image_id) REFERENCES `mdb_images` (`id`), + UNIQUE (value) +) WITH SYSTEM VERSIONING; + COMMIT; + BEGIN; INSERT INTO `mdb_licenses` (identifier, uri, description) @@ -523,7 +549,71 @@ INSERT INTO `mdb_images` (name, registry, version, default_port, dialect, driver VALUES ('mariadb', 'docker.io', '11.1.3', 3306, 'org.hibernate.dialect.MariaDBDialect', 'org.mariadb.jdbc.Driver', 'mariadb'); -INSERT INTO `mdb_ontologies` (prefix, uri, uri_pattern, sparql_endpoint, rdf_path) +INSERT INTO `mdb_image_types` (image_id, display_name, value, size_min, size_max, size_default, size_required, + size_step, d_min, d_max, d_default, d_required, d_step, hint, documentation, is_quoted, + is_buildable) +VALUES (1, 'BIGINT(size)', 'bigint', 0, null, null, false, 1, null, null, null, null, null, null, + 'https://mariadb.com/kb/en/bigint/', false, true), + (1, 'BINARY(size)', 'binary', 0, 255, 255, true, 1, null, null, null, null, null, 'size in Bytes', + 'https://mariadb.com/kb/en/binary/', false, true), + (1, 'BIT(size)', 'bit', 0, 64, null, false, 1, null, null, null, null, null, null, + 'https://mariadb.com/kb/en/bit/', false, true), + (1, 'BLOB(size)', 'blob', 0, 65535, null, false, 1, null, null, null, null, null, 'size in Bytes', + 'https://mariadb.com/kb/en/blob/', false, false), + (1, 'BOOL', 'bool', null, null, null, null, null, null, null, null, null, null, null, + 'https://mariadb.com/kb/en/bool/', false, true), + (1, 'CHAR(size)', 'char', 0, 255, 255, false, 1, null, null, null, null, null, null, + 'https://mariadb.com/kb/en/char/', false, true), + (1, 'DATE', 'date', null, null, null, null, null, null, null, null, null, null, + 'min. 1000-01-01, max. 9999-12-31', 'https://mariadb.com/kb/en/date/', true, true), + (1, 'DATETIME(fsp)', 'datetime', 0, 6, null, null, 1, null, null, null, null, null, + 'fsp=microsecond precision, min. 1000-01-01 00:00:00.0, max. 9999-12-31 23:59:59.9', + 'https://mariadb.com/kb/en/datetime/', true, true), + (1, 'DECIMAL(size, d)', 'decimal', 0, 65, null, false, 1, 0, 38, null, false, null, null, + 'https://mariadb.com/kb/en/decimal/', false, true), + (1, 'DOUBLE(size, d)', 'double', null, null, null, false, null, null, null, null, false, null, null, + 'https://mariadb.com/kb/en/double/', false, true), + (1, 'ENUM(v1,v2,...)', 'enum', null, null, null, null, null, null, null, null, null, null, null, + 'https://mariadb.com/kb/en/enum/', true, true), + (1, 'FLOAT(size)', 'float', null, null, null, false, null, null, null, null, null, null, null, + 'https://mariadb.com/kb/en/float/', false, true), + (1, 'INT(size)', 'int', null, null, null, false, null, null, null, null, null, null, 'size in Bytes', + 'https://mariadb.com/kb/en/int/', false, true), + (1, 'LONGBLOB', 'longblob', null, null, null, null, null, null, null, null, null, null, 'max. 3.999 GiB', + 'https://mariadb.com/kb/en/longblob/', false, true), + (1, 'LONGTEXT', 'longtext', null, null, null, null, null, null, null, null, null, null, 'max. 3.999 GiB', + 'https://mariadb.com/kb/en/longtext/', true, true), + (1, 'MEDIUMBLOB', 'mediumblob', null, null, null, null, null, null, null, null, null, null, 'max. 15.999 MiB', + 'https://mariadb.com/kb/en/mediumblob/', false, true), + (1, 'MEDIUMINT', 'mediumint', null, null, null, null, null, null, null, null, null, null, 'size in Bytes', + 'https://mariadb.com/kb/en/mediumint/', false, true), + (1, 'MEDIUMTEXT', 'mediumtext', null, null, null, null, null, null, null, null, null, null, 'size in Bytes', + 'https://mariadb.com/kb/en/mediumtext/', true, true), + (1, 'SET(v1,v2,...)', 'set', null, null, null, null, null, null, null, null, null, null, null, + 'https://mariadb.com/kb/en/set/', true, true), + (1, 'SMALLINT(size)', 'smallint', 0, null, null, false, null, null, null, null, null, null, 'size in Bytes', + 'https://mariadb.com/kb/en/smallint/', false, true), + (1, 'TEXT(size)', 'text', 0, null, null, false, null, null, null, null, null, null, 'size in Bytes', + 'https://mariadb.com/kb/en/text/', true, true), + (1, 'TIME(fsp)', 'time', 0, 6, 0, false, null, null, null, null, null, null, + 'fsp=microsecond precision, min. 0, max. 6', 'https://mariadb.com/kb/en/time/', true, true), + (1, 'TIMESTAMP(fsp)', 'timestamp', 0, 6, 0, false, null, null, null, null, null, null, + 'fsp=microsecond precision, min. 0, max. 6', 'https://mariadb.com/kb/en/timestamp/', true, true), + (1, 'TINYBLOB', 'tinyblob', null, null, null, null, null, null, null, null, null, null, + 'fsp=microsecond precision, min. 0, max. 6', 'https://mariadb.com/kb/en/timestamp/', false, true), + (1, 'TINYINT(size)', 'tinyint', 0, null, null, false, null, null, null, null, null, null, + 'size in Bytes', 'https://mariadb.com/kb/en/tinyint/', false, true), + (1, 'TINYTEXT', 'tinytext', null, null, null, null, null, null, null, null, null, null, + 'max. 255 characters', 'https://mariadb.com/kb/en/tinytext/', true, true), + (1, 'YEAR', 'year', 2, 4, null, false, 2, null, null, null, null, null, 'min. 1901, max. 2155', + 'https://mariadb.com/kb/en/year/', false, true), + (1, 'VARBINARY(size)', 'varbinary', 0, null, null, true, null, null, null, null, null, null, + null, 'https://mariadb.com/kb/en/varbinary/', false, true), + (1, 'VARCHAR(size)', 'varchar', 0, 65532, 255, true, null, null, null, null, null, null, + null, 'https://mariadb.com/kb/en/varchar/', false, true); + +INSERT +INTO `mdb_ontologies` (prefix, uri, uri_pattern, sparql_endpoint, rdf_path) VALUES ('om', 'http://www.ontology-of-units-of-measure.org/resource/om-2/', 'http://www.ontology-of-units-of-measure.org/resource/om-2/.*', null, 'rdf/om-2.0.rdf'), ('wd', 'http://www.wikidata.org/', 'http://www.wikidata.org/entity/.*', 'https://query.wikidata.org/sparql', diff --git a/dbrepo-metadata-service/api/pom.xml b/dbrepo-metadata-service/api/pom.xml index 4722e9c3c9..328fc8ee57 100644 --- a/dbrepo-metadata-service/api/pom.xml +++ b/dbrepo-metadata-service/api/pom.xml @@ -13,7 +13,14 @@ <name>dbrepo-metadata-service-api</name> <version>1.4.6</version> - <dependencies/> + <dependencies> + <dependency> + <groupId>at.tuwien</groupId> + <artifactId>dbrepo-metadata-service-entities</artifactId> + <version>1.4.6</version> + <scope>compile</scope> + </dependency> + </dependencies> <build> <plugins> diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/image/DataTypeDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/image/DataTypeDto.java new file mode 100644 index 0000000000..cd31aa2255 --- /dev/null +++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/image/DataTypeDto.java @@ -0,0 +1,66 @@ +package at.tuwien.api.container.image; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.*; +import lombok.extern.jackson.Jacksonized; + +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Jacksonized +@ToString +public class DataTypeDto { + + @NotBlank + @JsonProperty("display_name") + @Schema(example = "BIGINT") + private String displayName; + + @NotBlank + @Schema(example = "bigint") + private String value; + + @JsonProperty("size_min") + private Integer sizeMin; + + @JsonProperty("size_max") + private Integer sizeMax; + + @JsonProperty("size_default") + private Integer sizeDefault; + + @JsonProperty("size_required") + private Boolean sizeRequired; + + @JsonProperty("d_min") + private Integer dMin; + + @JsonProperty("d_max") + private Integer dMax; + + @JsonProperty("d_default") + private Integer dDefault; + + @JsonProperty("d_required") + private Boolean dRequired; + + @NotNull + @Schema(example = "https://mariadb.com/kb/en/bigint/") + private String documentation; + + @NotNull + @Schema(description = "frontend needs to quote this data type") + @JsonProperty("is_quoted") + private Boolean quoted; + + @NotNull + @JsonProperty("is_buildable") + @Schema(description = "frontend can build this data type") + private Boolean buildable; + +} diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/image/ImageDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/image/ImageDto.java index 2806bf071f..743f1f2b0a 100644 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/image/ImageDto.java +++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/image/ImageDto.java @@ -7,6 +7,8 @@ import jakarta.validation.constraints.NotNull; import lombok.*; import lombok.extern.jackson.Jacksonized; +import java.util.List; + @Getter @Setter @Builder @@ -55,4 +57,8 @@ public class ImageDto { @Schema(example = "3306") private Integer defaultPort; + @NotNull + @JsonProperty("data_types") + private List<DataTypeDto> dataTypes; + } diff --git a/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/container/image/ContainerImage.java b/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/container/image/ContainerImage.java index 9989b911ba..080a843aad 100644 --- a/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/container/image/ContainerImage.java +++ b/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/container/image/ContainerImage.java @@ -70,4 +70,8 @@ public class ContainerImage { @Column(columnDefinition = "TIMESTAMP") private Instant lastModified; + @ToString.Exclude + @OneToMany(fetch = FetchType.LAZY, mappedBy = "image") + private List<DataType> dataTypes; + } diff --git a/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/container/image/DataType.java b/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/container/image/DataType.java new file mode 100644 index 0000000000..8333470ad3 --- /dev/null +++ b/dbrepo-metadata-service/entities/src/main/java/at/tuwien/entities/container/image/DataType.java @@ -0,0 +1,73 @@ +package at.tuwien.entities.container.image; + +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.GenericGenerator; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.util.List; + +@Data +@Entity +@Builder +@ToString +@AllArgsConstructor +@NoArgsConstructor +@EntityListeners(AuditingEntityListener.class) +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@Table(name = "mdb_image_types") +public class DataType { + + @Id + @EqualsAndHashCode.Include + @GeneratedValue(generator = "image-type-sequence") + @GenericGenerator(name = "image-type-sequence", strategy = "increment") + @Column(updatable = false, nullable = false) + public Long id; + + @Column(name = "display_name", nullable = false) + private String displayName; + + @Column(name = "value", nullable = false, unique = true) + private String value; + + @Column(name = "size_min", nullable = false) + private Integer sizeMin; + + @Column(name = "size_max") + private Integer sizeMax; + + @Column(name = "size_default") + private Integer sizeDefault; + + @Column(name = "size_required", nullable = false) + private Boolean sizeRequired; + + @Column(name = "d_min") + private Integer dMin; + + @Column(name = "d_max") + private Integer dMax; + + @Column(name = "d_default") + private Integer dDefault; + + @Column(name = "d_required", nullable = false) + private Boolean dRequired; + + @Column(nullable = false) + private String documentation; + + @Column(name = "is_quoted", nullable = false) + private Boolean quoted; + + @Column(name = "is_buildable", nullable = false) + private Boolean buildable; + + @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.ALL, CascadeType.PERSIST}) + @JoinColumns({ + @JoinColumn(name = "image_id", referencedColumnName = "id") + }) + private ContainerImage image; + +} diff --git a/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/MetadataMapper.java b/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/MetadataMapper.java index f6c96f133d..0555e1c62c 100644 --- a/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/MetadataMapper.java +++ b/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/MetadataMapper.java @@ -4,6 +4,7 @@ import at.tuwien.api.auth.SignupRequestDto; import at.tuwien.api.container.ContainerBriefDto; import at.tuwien.api.container.ContainerCreateDto; import at.tuwien.api.container.ContainerDto; +import at.tuwien.api.container.image.DataTypeDto; import at.tuwien.api.container.image.ImageBriefDto; import at.tuwien.api.container.image.ImageCreateDto; import at.tuwien.api.container.image.ImageDto; @@ -55,6 +56,7 @@ import at.tuwien.api.user.external.ExternalResultType; import at.tuwien.api.user.external.affiliation.ExternalAffiliationDto; import at.tuwien.entities.container.Container; import at.tuwien.entities.container.image.ContainerImage; +import at.tuwien.entities.container.image.DataType; import at.tuwien.entities.database.*; import at.tuwien.entities.database.table.Table; import at.tuwien.entities.database.table.columns.TableColumn; @@ -86,6 +88,14 @@ public interface MetadataMapper { org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MetadataMapper.class); + @Mappings({ + @Mapping(target = "dMin", source = "DMin"), + @Mapping(target = "dMax", source = "DMax"), + @Mapping(target = "dDefault", source = "DDefault"), + @Mapping(target = "dRequired", source = "DRequired") + }) + DataTypeDto dataTypeToDataTypeDto(DataType data); + BannerMessageDto bannerMessageToBannerMessageDto(BannerMessage data); BannerMessageBriefDto bannerMessageToBannerMessageBriefDto(BannerMessage data); diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java index e89a38b6f6..d5c316fed9 100644 --- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java +++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java @@ -3,6 +3,7 @@ package at.tuwien.endpoints; import at.tuwien.api.database.*; import at.tuwien.api.error.ApiErrorDto; import at.tuwien.entities.container.Container; +import at.tuwien.entities.container.image.DataType; import at.tuwien.entities.database.Database; import at.tuwien.entities.database.DatabaseAccess; import at.tuwien.entities.user.User; diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java index a3c0383118..738e30d4e4 100644 --- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java +++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java @@ -3,9 +3,7 @@ package at.tuwien.endpoints; import at.tuwien.api.database.table.TableBriefDto; import at.tuwien.api.database.table.TableCreateDto; import at.tuwien.api.database.table.TableDto; -import at.tuwien.api.database.table.columns.ColumnCreateDto; import at.tuwien.api.database.table.columns.ColumnDto; -import at.tuwien.api.database.table.columns.ColumnTypeDto; import at.tuwien.api.database.table.columns.concepts.ColumnSemanticsUpdateDto; import at.tuwien.api.error.ApiErrorDto; import at.tuwien.api.semantics.EntityDto; @@ -16,7 +14,10 @@ import at.tuwien.entities.database.table.columns.TableColumn; import at.tuwien.entities.user.User; import at.tuwien.exception.*; import at.tuwien.mapper.MetadataMapper; -import at.tuwien.service.*; +import at.tuwien.service.DatabaseService; +import at.tuwien.service.EntityService; +import at.tuwien.service.TableService; +import at.tuwien.service.UserService; import at.tuwien.utils.UserUtil; import at.tuwien.validation.EndpointValidator; import io.micrometer.observation.annotation.Observed; @@ -39,7 +40,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import java.security.Principal; -import java.util.*; +import java.util.List; import java.util.stream.Collectors; @Log4j2 diff --git a/dbrepo-metadata-service/rest-service/src/main/resources/datatypes.json b/dbrepo-metadata-service/rest-service/src/main/resources/datatypes.json new file mode 100644 index 0000000000..3779d12cbe --- /dev/null +++ b/dbrepo-metadata-service/rest-service/src/main/resources/datatypes.json @@ -0,0 +1,15 @@ +[ + { + "name": "", + "size": { + "min": 0, + "required": true + }, + "d": { + "required": false + }, + "documentation": "https://mariadb.com/kb/en/bigint/", + "quoted": false, + "buildable": true + } +] \ No newline at end of file diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java index 8d24691467..0013f01563 100644 --- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java +++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/TableServiceImpl.java @@ -10,7 +10,6 @@ import at.tuwien.entities.database.Database; import at.tuwien.entities.database.table.Table; import at.tuwien.entities.database.table.columns.TableColumn; import at.tuwien.entities.database.table.columns.TableColumnConcept; -import at.tuwien.entities.database.table.columns.TableColumnType; import at.tuwien.entities.database.table.columns.TableColumnUnit; import at.tuwien.entities.user.User; import at.tuwien.exception.*; @@ -25,7 +24,10 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.security.Principal; -import java.util.*; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; @Log4j2 @Service @@ -156,6 +158,8 @@ public class TableServiceImpl implements TableService { for (int i = 0; i < data.getConstraints().getUniques().size(); i++) { if (data.getConstraints().getUniques().get(i).size() != table.getConstraints().getUniques().get(i).getColumns().size()) { log.error("Failed to create table: some unique constraint(s) reference non-existing table columns: {}", data.getConstraints().getUniques().get(i)); + log.debug("payload uniques: {}", data.getConstraints().getUniques()); + log.debug("mapped table uniques: {}", table.getConstraints().getUniques().stream().map(u -> List.of(u.getColumns().stream().map(TableColumn::getInternalName).toList())).toList()); throw new MalformedException("Failed to create table: some unique constraint(s) reference non-existing table columns"); } } diff --git a/dbrepo-ui/components/subset/Builder.vue b/dbrepo-ui/components/subset/Builder.vue index 7c30fd8f5a..9ae9916203 100644 --- a/dbrepo-ui/components/subset/Builder.vue +++ b/dbrepo-ui/components/subset/Builder.vue @@ -370,6 +370,12 @@ export default { database () { return this.cacheStore.getDatabase }, + columnTypes () { + if (!this.database) { + return [] + } + return this.database.container.image.data_types + }, user () { return this.userStore.getUser }, @@ -533,7 +539,7 @@ export default { return } const queryService = useQueryService() - const { error, reason, column, raw, formatted } = queryService.build(this.table.internal_name, this.select, this.clauses) + const { error, reason, column, raw, formatted } = queryService.build(this.table.internal_name, this.select, this.columnTypes, this.clauses) if (error) { const toast = useToastInstance() toast.error(this.$t('error.query.' + reason) + ' ' + column) diff --git a/dbrepo-ui/components/table/TableSchema.vue b/dbrepo-ui/components/table/TableSchema.vue index 8e6f3c404c..5e105577d7 100644 --- a/dbrepo-ui/components/table/TableSchema.vue +++ b/dbrepo-ui/components/table/TableSchema.vue @@ -14,7 +14,10 @@ <v-text-field v-model="c.name" required - :rules="[v => !!v || $t('validation.required')]" + :rules="[ + v => !!v || $t('validation.required'), + v => this.columns.filter(column => column.name === v).length === 1 || $t('validation.column.exists') + ]" persistent-hint :variant="inputVariant" :label="$t('pages.table.subpages.schema.name.label')" @@ -25,7 +28,7 @@ <v-select v-model="c.type" :items="columnTypes" - item-title="text" + item-title="display_name" item-value="value" required :rules="[v => !!v || $t('validation.required')]" @@ -68,32 +71,40 @@ @focusout="formatValues(c)" /> </v-col> <v-col - v-if="defaultSize(c) !== false || hasMinSize(c) || hasMaxSize(c)" + v-if="columnType(c) && columnType(c).size_required !== null" cols="1"> <v-text-field v-model.number="c.size" type="number" - required - :min="hasMinSize(c) ? minSize(c) : null" - :max="hasMaxSize(c) ? maxSize(c) : null" - :step="sizeSteps(c)" + :min="columnType(c).size_min !== null ? columnType(c).size_min : null" + :max="columnType(c).size_max !== null ? columnType(c).size_max : null" + :step="columnType(c).size_step" :hint="sizeHint(c)" - :clearable="!optionalSize(c)" + :clearable="!columnType(c).size_required" persistent-hint :variant="inputVariant" - :rules="[v => !(!defaultSize(c) && (v === null || v === '')) || $t('validation.required')]" + :rules="[ + v => !(columnType(c).size_required && (v === null || v === '')) || $t('validation.required') + ]" :error-messages="sizeErrorMessages(c)" :label="$t('pages.table.subpages.schema.size.label')" /> </v-col> <v-col - v-if="defaultD(c) !== false" + v-if="columnType(c) && columnType(c).d_required !== null" cols="1"> <v-text-field v-model.number="c.d" type="number" - required + :min="columnType(c).d_min !== null ? columnType(c).d_min : null" + :max="columnType(c).d_max !== null ? columnType(c).d_max : null" + :step="columnType(c).d_step" + :hint="dHint(c)" + :clearable="!columnType(c).d_required" + persistent-hint :variant="inputVariant" - :rules="[v => (v !== null && v !== '') || $t('validation.required')]" + :rules="[ + v => !(columnType(c).d_required && (v === null || v === '')) || $t('validation.required') + ]" :error-messages="dErrorMessages(c)" :label="$t('pages.table.subpages.schema.d.label')" /> </v-col> @@ -211,7 +222,6 @@ export default { return { valid: false, tableColumns: [], - columnTypes: useQueryService().mySql8DataTypes(), cacheStore: useCacheStore() } }, @@ -219,6 +229,12 @@ export default { database () { return this.cacheStore.getDatabase }, + columnTypes () { + if (!this.database) { + return [] + } + return this.database.container.image.data_types + }, dateFormats () { if (!this.database || !('container' in this.database) || !('image' in this.database.container) || !('date_formats' in this.database.container.image)) { return [] @@ -250,16 +266,10 @@ export default { return false } let shift = 0 - if (this.hasDate(column) === false && this.columns.filter(c => this.hasDate(c) !== false).length > 0) { - shift++ - } - if (this.defaultSize(column) === false && this.columns.filter(c => this.defaultSize(c) !== false).length > 0) { - shift++ - } - if (this.defaultD(column) === false && this.columns.filter(c => this.defaultD(c) !== false).length > 0) { + if (!this.hasEnumOrSet(column) && (this.columnType(column).size_required === null || this.columnType(column).size_required === undefined) && this.columns.filter(c => (this.columnType(c).size_required !== null || this.columnType(c).size_required !== undefined)).length > 0) { shift++ } - if (this.hasEnumOrSet(column) === false && this.columns.filter(c => this.hasEnumOrSet(c) !== false).length > 0) { + if (!this.hasEnumOrSet(column) && (this.columnType(column).d_required === null || this.columnType(column).d_required === undefined) && this.columns.filter(c => (this.columnType(c).d_required !== null || this.columnType(c).d_required !== undefined)).length > 0) { shift++ } return shift @@ -312,99 +322,56 @@ export default { column.enums = column.enums_values.split(',').map(v => v.trim()) } }, - defaultSize (column) { + columnType (column) { const filter = this.columnTypes.filter(t => t.value === column.type) if (!filter || filter.length === 0) { return false } - if (filter[0].defaultSize === undefined || filter[0].defaultSize === null) { - return false - } - return filter[0].defaultSize - }, - requiredSize (column) { - const filter = this.columnTypes.filter(t => t.value === column.type) - if (!filter || filter.length === 0) { - return false - } - if (filter[0].requiredSize === undefined || filter[0].requiredSize === null) { - return false - } - return filter[0].requiredSize + return filter[0] }, - hasMinSize (column) { - const filter = this.columnTypes.filter(t => t.value === column.type) - if (!filter || filter.length === 0) { - return false - } - return filter[0].minSize !== undefined - }, - minSize (column) { - const filter = this.columnTypes.filter(t => t.value === column.type) - if (!filter || filter.length === 0) { - return false - } - if (filter[0].minSize === undefined || filter[0].minSize === null) { - return false - } - return filter[0].minSize - }, - hasMaxSize (column) { - const filter = this.columnTypes.filter(t => t.value === column.type) - if (!filter || filter.length === 0) { - return false - } - return filter[0].maxSize !== undefined - }, - maxSize (column) { - const filter = this.columnTypes.filter(t => t.value === column.type) - if (!filter || filter.length === 0) { - return false - } - if (filter[0].maxSize === undefined || filter[0].maxSize === null) { - return false + sizeHint (column) { + let hint = '' + if (this.columnType(column).size_min !== null) { + hint += `min. ${this.columnType(column).size_min}` } - return filter[0].maxSize - }, - sizeSteps (column) { - const filter = this.columnTypes.filter(t => t.value === column.type) - if (!filter || filter.length === 0) { - return null + if (this.columnType(column).size_max) { + if (hint.length > 0) { + hint += ', ' + } + hint += `max. ${this.columnType(column).size_max}` } - if (filter[0].sizeSteps === undefined || filter[0].sizeSteps === null) { - return 1 + if (!this.columnType(column).size_required) { + hint += ' (optional)' } - return filter[0].sizeSteps + return hint }, - sizeHint (column) { + dHint (column) { let hint = '' - if (this.hasMinSize(column)) { - hint += `min. ${this.minSize(column)}` + if (this.columnType(column).d_min !== null) { + hint += `min. ${this.columnType(column).d_min}` } - if (this.hasMaxSize(column)) { + if (this.columnType(column).d_max) { if (hint.length > 0) { hint += ', ' } - hint += `max. ${this.maxSize(column)}` + hint += `max. ${this.columnType(column).d_max}` } - if (!this.defaultSize(column)) { + if (!this.columnType(column).d_required) { hint += ' (optional)' } return hint }, - defaultD (column) { - const filter = this.columnTypes.filter(t => t.value === column.type) - if (!filter || filter.length === 0) { - return false + setDefaultSizeAndD (column) { + if (this.columnType(column).size_default !== null) { + column.size = this.columnType(column).size_default + } else { + column.size = null } - if (filter[0].defaultD === undefined || filter[0].defaultD === null) { - return false + if (this.columnType(column).d_default !== null) { + column.d = this.columnType(column).d_default + } else { + column.d = null } - return filter[0].defaultD - }, - setDefaultSizeAndD (column) { - column.size = this.defaultSize(column) - column.d = this.defaultD(column) console.debug('for column type', column.type, 'set default size', column.size, '& d', column.d) }, hasDate (column) { diff --git a/dbrepo-ui/composables/query-service.ts b/dbrepo-ui/composables/query-service.ts index 00d661788d..b3c21c6053 100644 --- a/dbrepo-ui/composables/query-service.ts +++ b/dbrepo-ui/composables/query-service.ts @@ -126,7 +126,7 @@ export const useQueryService = (): any => { }) } - function build(table: TableDto, columns: ColumnDto[], clauses: any[]): QueryBuildResultDto { + function build(table: TableDto, columns: ColumnDto[], types: DataTypeDto[], clauses: any[]): QueryBuildResultDto { var sql = 'SELECT' for (let i = 0; i < columns.length; i++) { sql += `${i > 0 ? ',' : ''} \`${columns[i].internal_name}\`` @@ -140,8 +140,8 @@ export const useQueryService = (): any => { sql += ` ${clause.type.toUpperCase()} ` continue } - const fCol = columns.filter(c => c.internal_name === clause.params[0]) - if (fCol.length === 0) { + const filteredColumn = columns.filter(c => c.internal_name === clause.params[0]) + if (filteredColumn.length === 0) { return { error: true, reason: 'column.exists', @@ -151,26 +151,26 @@ export const useQueryService = (): any => { } } sql += ` \`${clause.params[0]}\` ${clause.params[1]} ` - const fCon = mySql8DataTypes().filter(t => t.value === fCol[0].column_type) - if (fCol.length === 0) { + const filteredType = types.filter(t => t.value === filteredColumn[0].column_type) + if (filteredType.length === 0) { return { error: true, - reason: 'type.exists', - column: fCol[0].column_type, + reason: 'exists', + column: filteredColumn[0].column_type, raw: null, formatted: null } } - if (!fCon[0].isBuildable) { + if (!filteredType[0].is_buildable) { return { error: true, - reason: 'type.build', - column: fCol[0].column_type, + reason: 'build', + column: filteredColumn[0].column_type, raw: null, formatted: null } } - if (fCon[0].quoted) { + if (filteredType[0].is_quoted) { sql += `'${clause.params[2]}'` } else { sql += `${clause.params[2]}` @@ -196,42 +196,5 @@ export const useQueryService = (): any => { return {timestamp, page, size} } - /** - * data types with non-null defaultSize values require size to be set - */ - function mySql8DataTypes(): MySql8DataType[] { - return [ - {value: 'bigint', text: 'BIGINT(size)', defaultSize: null, defaultD: null, signed: null, zerofill: false, quoted: false, isBuildable: true, hint: null}, - {value: 'binary', text: 'BINARY(size)', minSize: 0, maxSize: 255, defaultSize: 255, defaultD: null, quoted: false, isBuildable: false, hint: 'size in Bytes'}, - {value: 'bit', text: 'BIT(size)', minSize: 1, maxSize: 64, defaultSize: null, defaultD: null, quoted: false, isBuildable: true, hint: null}, - {value: 'blob', text: 'BLOB(size)', minSize: 0, maxSize: 65535, defaultSize: null, defaultD: null, quoted: false, isBuildable: false, hint: 'size in Bytes'}, - {value: 'bool', text: 'BOOL', defaultSize: null, defaultD: null, quoted: false, isBuildable: true}, - {value: 'char', text: 'CHAR(size)', minSize: 0, maxSize: 255, defaultSize: 255, defaultD: null, quoted: true, isBuildable: true}, - {value: 'date', text: 'DATE', defaultSize: null, defaultD: null, quoted: true, isBuildable: true, hint: 'min. 1000-01-01, max. 9999-12-31'}, - {value: 'datetime', text: 'DATETIME(fsp)', minSize: 0, maxSize: 6, defaultSize: null, defaultD: null, quoted: true, isBuildable: true, hint: 'fsp=microsecond precision, min. 1000-01-01 00:00:00.0, max. 9999-12-31 23:59:59.9'}, - {value: 'decimal', text: 'DECIMAL(size, d)', minSize: 0, maxSize: 65, defaultSize: null, defaultD: null, minD: 0, maxD: 38, signed: null, quoted: false, isBuildable: true}, - {value: 'double', text: 'DOUBLE(size, d)', defaultSize: null, defaultD: null, signed: null, quoted: false, isBuildable: true}, - {value: 'enum', text: 'ENUM(val1,val2,...)', defaultSize: null, defaultD: null, quoted: true, isBuildable: true}, - {value: 'float', text: 'FLOAT(p)', defaultSize: null, defaultD: null, signed: null, quoted: false, isBuildable: true}, - {value: 'int', text: 'INT(size)', defaultSize: null, defaultD: null, signed: null, zerofill: false, quoted: false, isBuildable: true, hint: 'size in Bytes'}, - {value: 'longblob', text: 'LONGBLOB', defaultSize: null, defaultD: null, quoted: false, isBuildable: false, hint: 'max. 3.999 GiB'}, - {value: 'longtext', text: 'LONGTEXT', defaultSize: null, defaultD: null, quoted: true, isBuildable: true, hint: 'max. 3.999 GiB'}, - {value: 'mediumblob', text: 'MEDIUMBLOB', defaultSize: null, defaultD: null, quoted: false, isBuildable: false, hint: 'max. 15.999 MiB'}, - {value: 'mediumint', text: 'MEDIUMINT(size)', defaultSize: null, defaultD: null, signed: null, zerofill: false, quoted: false, isBuildable: true, hint: 'size in Bytes'}, - {value: 'mediumtext', text: 'MEDIUMTEXT', defaultSize: null, defaultD: null, quoted: true, isBuildable: true}, - {value: 'set', text: 'SET(val1,val2,...)', defaultSize: null, defaultD: null, quoted: true, isBuildable: true}, - {value: 'smallint', text: 'SMALLINT(size)', defaultSize: null, defaultD: null, signed: null, zerofill: false, quoted: false, isBuildable: true, hint: 'size in Bytes'}, - {value: 'text', text: 'TEXT(size)', minSize: 0, defaultSize: null, defaultD: null, quoted: true, isBuildable: true, hint: 'size in #characters'}, - {value: 'time', text: 'TIME(fsp)', minSize: 0, maxSize: 6, defaultSize: 0, defaultD: null, quoted: true, isBuildable: true, hint: 'fsp=microsecond precision, min. 0, max. 6'}, - {value: 'timestamp', text: 'TIMESTAMP(fsp)', minSize: 0, maxSize: 6, defaultSize: 0, defaultD: null, quoted: true, isBuildable: true, hint: 'fsp=microsecond precision, min. 0, max. 6'}, - {value: 'tinyblob', text: 'TINYBLOB', defaultSize: null, defaultD: null, quoted: false, isBuildable: false}, - {value: 'tinyint', text: 'TINYINT(size)', defaultSize: null, defaultD: null, quoted: false, isBuildable: true, hint: 'size in Bytes'}, - {value: 'tinytext', text: 'TINYTEXT', defaultSize: null, defaultD: null, quoted: true, isBuildable: true, hint: 'max. 255 characters'}, - {value: 'year', text: 'YEAR', minSize: 2, maxSize: 4, sizeSteps: 2, defaultSize: null, defaultD: null, quoted: true, isBuildable: true, hint: 'min. 1901, max. 2155'}, - {value: 'varbinary', text: 'VARBINARY(size)', defaultSize: null, defaultD: null, quoted: false, isBuildable: false}, - {value: 'varchar', text: 'VARCHAR(size)', minSize: 0, maxSize: 65532, defaultSize: 255, defaultD: null, quoted: true, isBuildable: true, hint: 'max. characters depends on the encoding'} - ] - } - - return {findAll, findOne, update, exportCsv, execute, reExecuteData, reExecuteCount, build, mySql8DataTypes} + return {findAll, findOne, update, exportCsv, execute, reExecuteData, reExecuteCount, build} } diff --git a/dbrepo-ui/dto/mysql.ts b/dbrepo-ui/dto/mysql.ts index b100da017c..c366e43f64 100644 --- a/dbrepo-ui/dto/mysql.ts +++ b/dbrepo-ui/dto/mysql.ts @@ -1,8 +1,15 @@ -interface MySql8DataType { +interface DataTypeDto { + display_name: string; value: string; - text: string; - defaultSize: number | null; - defaultD: number | null; - quoted: boolean; - isBuildable: boolean; + size_min: number | null; + size_max: number | null; + size_default: number | null; + size_required: number | null; + d_min: number | null; + d_max: number | null; + d_default: number | null; + d_required: number | null; + documentation: string; + is_quoted: boolean; + is_buildable: boolean; } diff --git a/dbrepo-ui/locales/en-US.json b/dbrepo-ui/locales/en-US.json index 4eb7316617..db2c41bfd2 100644 --- a/dbrepo-ui/locales/en-US.json +++ b/dbrepo-ui/locales/en-US.json @@ -1142,8 +1142,8 @@ "query": { "missing": "Failed to find query in data service", "invalid": "Query is invalid", - "type.exists": "Failed to build query: no such column type", - "type.build": "Failed to build query: currently no query build support for column type", + "exists": "Failed to build query: no such column type", + "build": "Failed to build query: currently no query build support for column type", "column.exists": "Failed to build query: data columns are missing column with name" }, "store": { @@ -1440,6 +1440,9 @@ "pattern": "Invalid URI", "exists": "URI exists" }, + "column": { + "exists": "Column with this name exists" + }, "user": { "pattern": "Only lowercase letters, min. 3 length", "exists": "This username is already taken" diff --git a/dbrepo-ui/nuxt.config.ts b/dbrepo-ui/nuxt.config.ts index 1e725d6213..80b866f1c1 100644 --- a/dbrepo-ui/nuxt.config.ts +++ b/dbrepo-ui/nuxt.config.ts @@ -108,7 +108,7 @@ export default defineNuxtConfig({ }, grafana: { text: 'Dashboard Service', - href: 'http://localhost:3000/dashboard/' + href: 'http://localhost:3000/dashboards' } } } diff --git a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/schema.vue b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/schema.vue index 3a821a730b..7e8aed6717 100644 --- a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/schema.vue +++ b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/schema.vue @@ -217,12 +217,20 @@ export default { }, methods: { extra (column) { - if (['date', 'datetime', 'timestamp', 'time'].includes(column.column_type)) { - return `fsp=${column.date_format.unix_format}` - } else if (column.column_type === 'float') { - return `p=${column.size}` + if (column.column_type === 'float') { + return `precision=${column.size}` } else if (['decimal', 'double'].includes(column.column_type)) { - return `size=${column.size} d=${column.d}` + let extra = '' + if (column.size !== null) { + extra += `size=${column.size}` + } + if (column.d !== null) { + if (extra.length > 0) { + extra += ', ' + } + extra += `d=${column.d}` + } + return extra } else if (column.column_type === 'enum') { return `(${column.enums.join(', ')})` } else if (column.column_type === 'set') { -- GitLab