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