diff --git a/fda-database-service/rest-service/src/main/resources/init/querystore.sql b/fda-database-service/rest-service/src/main/resources/init/querystore.sql index c98ce27ecf601053d39820a82810958644e0254f..26fc6e54370ec268fa2cfa683f37c825d7cfa81b 100644 --- a/fda-database-service/rest-service/src/main/resources/init/querystore.sql +++ b/fda-database-service/rest-service/src/main/resources/init/querystore.sql @@ -1,5 +1,5 @@ -CREATE SEQUENCE `qs_queries_seq`; -CREATE TABLE `qs_queries`( `id` bigint not null primary key default nextval(`qs_queries_seq`), `created` datetime not null default now(), `executed` datetime not null default now(), `created_by` varchar(255) not null, `query` text not null, `query_normalized` text not null, `is_persisted` boolean not null, `query_hash` varchar(255) not null, `result_hash` varchar(255), `result_number` bigint); -CREATE PROCEDURE hash_table(IN name VARCHAR(255), OUT hash VARCHAR(255))BEGIN DECLARE _sql TEXT; SELECT CONCAT('SELECT SHA2(GROUP_CONCAT(CONCAT_WS(\'\',', GROUP_CONCAT(CONCAT('`', column_name, '`') ORDER BY column_name), ') SEPARATOR \',\'), 256) AS hash FROM `', name, '` INTO @hash;') FROM `information_schema`.`columns` WHERE `table_schema` = DATABASE() AND `table_name` = name INTO _sql; PREPARE stmt FROM _sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET hash = @hash;END -CREATE PROCEDURE store_query(IN query TEXT, IN executed DATETIME, OUT queryId BIGINT)BEGIN DECLARE _queryhash varchar(255) DEFAULT SHA2(query, 256); DECLARE _username varchar(255) DEFAULT REGEXP_REPLACE(current_user(), '@.*', ''); DECLARE _query TEXT DEFAULT CONCAT('CREATE OR REPLACE TABLE _tmp AS (', query, ')'); PREPARE stmt FROM _query; EXECUTE stmt; DEALLOCATE PREPARE stmt; CALL hash_table('_tmp', @hash); SELECT COUNT(*) FROM _tmp INTO @count; IF @hash IS NULL THEN INSERT INTO `qs_queries` (`created_by`, `query`, `query_normalized`, `is_persisted`, `query_hash`, `result_hash`, `result_number`, `executed`) SELECT _username, query, query, false, _queryhash, @hash, @count, executed WHERE NOT EXISTS (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` IS NULL); SET queryId = (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` IS NULL); ELSE INSERT INTO `qs_queries` (`created_by`, `query`, `query_normalized`, `is_persisted`, `query_hash`, `result_hash`, `result_number`, `executed`) SELECT _username, query, query, false, _queryhash, @hash, @count, executed WHERE NOT EXISTS (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` = @hash); SET queryId = (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` = @hash); END IF; END -CREATE DEFINER = 'root' PROCEDURE _store_query(IN _username VARCHAR(255), IN query TEXT, IN executed DATETIME, OUT queryId BIGINT)BEGIN DECLARE _queryhash varchar(255) DEFAULT SHA2(query, 256); DECLARE _query TEXT DEFAULT CONCAT('CREATE OR REPLACE TABLE _tmp AS (', query, ')'); PREPARE stmt FROM _query; EXECUTE stmt; DEALLOCATE PREPARE stmt; CALL hash_table('_tmp', @hash); SELECT COUNT(*) FROM _tmp INTO @count; IF @hash IS NULL THEN INSERT INTO `qs_queries` (`created_by`, `query`, `query_normalized`, `is_persisted`, `query_hash`, `result_hash`, `result_number`, `executed`) SELECT _username, query, query, false, _queryhash, @hash, @count, executed WHERE NOT EXISTS (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` IS NULL); SET queryId = (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` IS NULL); ELSE INSERT INTO `qs_queries` (`created_by`, `query`, `query_normalized`, `is_persisted`, `query_hash`, `result_hash`, `result_number`, `executed`) SELECT _username, query, query, false, _queryhash, @hash, @count, executed WHERE NOT EXISTS (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` = @hash); SET queryId = (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` = @hash); END IF; END \ No newline at end of file +CREATE SEQUENCE `qs_queries_seq`; +CREATE TABLE `qs_queries` ( `id` bigint not null primary key default nextval(`qs_queries_seq`), `created` datetime not null default now(), `executed` datetime not null default now(), `created_by` varchar(255) not null, `query` text not null, `query_normalized` text not null, `is_persisted` boolean not null, `query_hash` varchar(255) not null, `result_hash` varchar(255), `result_number` bigint ); +CREATE PROCEDURE hash_table(IN name VARCHAR(255), OUT hash VARCHAR(255), OUT count BIGINT) BEGIN DECLARE _sql TEXT; SELECT CONCAT('SELECT SHA2(GROUP_CONCAT(CONCAT_WS(\'\',', GROUP_CONCAT(CONCAT('`', column_name, '`') ORDER BY column_name), ') SEPARATOR \',\'), 256) AS hash, COUNT(*) AS count FROM `', name, '` INTO @hash, @count;') FROM `information_schema`.`columns` WHERE `table_schema` = DATABASE() AND `table_name` = name INTO _sql; PREPARE stmt FROM _sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET hash = @hash; SET count = @count; END; +CREATE PROCEDURE store_query(IN query TEXT, IN executed DATETIME, OUT queryId BIGINT) BEGIN DECLARE _queryhash varchar(255) DEFAULT SHA2(query, 256); DECLARE _username varchar(255) DEFAULT REGEXP_REPLACE(current_user(), '@.*', ''); DECLARE _query TEXT DEFAULT CONCAT('CREATE OR REPLACE TABLE _tmp AS (', query, ')'); PREPARE stmt FROM _query; EXECUTE stmt; DEALLOCATE PREPARE stmt; CALL hash_table('_tmp', @hash, @count); IF @hash IS NULL THEN INSERT INTO `qs_queries` (`created_by`, `query`, `query_normalized`, `is_persisted`, `query_hash`, `result_hash`, `result_number`, `executed`) SELECT _username, query, query, false, _queryhash, @hash, @count, executed WHERE NOT EXISTS (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` IS NULL); SET queryId = (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` IS NULL); ELSE INSERT INTO `qs_queries` (`created_by`, `query`, `query_normalized`, `is_persisted`, `query_hash`, `result_hash`, `result_number`, `executed`) SELECT _username, query, query, false, _queryhash, @hash, @count, executed WHERE NOT EXISTS (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` = @hash); SET queryId = (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` = @hash); END IF; END; +CREATE DEFINER = 'root' PROCEDURE _store_query(IN _username VARCHAR(255), IN query TEXT, IN executed DATETIME, OUT queryId BIGINT) BEGIN DECLARE _queryhash varchar(255) DEFAULT SHA2(query, 256); DECLARE _query TEXT DEFAULT CONCAT('CREATE OR REPLACE TABLE _tmp AS (', query, ')'); PREPARE stmt FROM _query; EXECUTE stmt; DEALLOCATE PREPARE stmt; CALL hash_table('_tmp', @hash, @count); IF @hash IS NULL THEN INSERT INTO `qs_queries` (`created_by`, `query`, `query_normalized`, `is_persisted`, `query_hash`, `result_hash`, `result_number`, `executed`) SELECT _username, query, query, false, _queryhash, @hash, @count, executed WHERE NOT EXISTS (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` IS NULL); SET queryId = (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` IS NULL); ELSE INSERT INTO `qs_queries` (`created_by`, `query`, `query_normalized`, `is_persisted`, `query_hash`, `result_hash`, `result_number`, `executed`) SELECT _username, query, query, false, _queryhash, @hash, @count, executed WHERE NOT EXISTS (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` = @hash); SET queryId = (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` = @hash); END IF; END; \ No newline at end of file diff --git a/fda-database-service/rest-service/src/main/resources/init/querystore_manual.sql b/fda-database-service/rest-service/src/main/resources/init/querystore_manual.sql index 2762d130a0044c439b41c0215b0d87924bc8f072..5580d0fd260b6ba4fddd24b411a6e74467d151d2 100644 --- a/fda-database-service/rest-service/src/main/resources/init/querystore_manual.sql +++ b/fda-database-service/rest-service/src/main/resources/init/querystore_manual.sql @@ -1,6 +1,5 @@ CREATE SEQUENCE `qs_queries_seq`; -CREATE TABLE `qs_queries` -( +CREATE TABLE `qs_queries` ( `id` bigint not null primary key default nextval(`qs_queries_seq`), `created` datetime not null default now(), `executed` datetime not null default now(), @@ -12,13 +11,14 @@ CREATE TABLE `qs_queries` `result_hash` varchar(255), `result_number` bigint ); + DELIMITER $$ -CREATE PROCEDURE hash_table(IN name VARCHAR(255), OUT hash VARCHAR(255)) +CREATE PROCEDURE hash_table(IN name VARCHAR(255), OUT hash VARCHAR(255), OUT count BIGINT) BEGIN DECLARE _sql TEXT; SELECT CONCAT('SELECT SHA2(GROUP_CONCAT(CONCAT_WS(\'\',', GROUP_CONCAT(CONCAT('`', column_name, '`') ORDER BY column_name), - ') SEPARATOR \',\'), 256) AS hash FROM `', name, '` INTO @hash;') + ') SEPARATOR \',\'), 256) AS hash, COUNT(*) AS count FROM `', name, '` INTO @hash, @count;') FROM `information_schema`.`columns` WHERE `table_schema` = DATABASE() AND `table_name` = name @@ -27,15 +27,16 @@ BEGIN EXECUTE stmt; DEALLOCATE PREPARE stmt; SET hash = @hash; -END $$ + SET count = @count; +END; $$ + DELIMITER $$ CREATE PROCEDURE store_query(IN query TEXT, IN executed DATETIME, OUT queryId BIGINT) BEGIN DECLARE _queryhash varchar(255) DEFAULT SHA2(query, 256); DECLARE _username varchar(255) DEFAULT REGEXP_REPLACE(current_user(), '@.*', ''); DECLARE _query TEXT DEFAULT CONCAT('CREATE OR REPLACE TABLE _tmp AS (', query, ')'); - PREPARE stmt FROM _query; EXECUTE stmt; DEALLOCATE PREPARE stmt; CALL hash_table('_tmp', @hash); - SELECT COUNT(*) FROM _tmp INTO @count; + PREPARE stmt FROM _query; EXECUTE stmt; DEALLOCATE PREPARE stmt; CALL hash_table('_tmp', @hash, @count); IF @hash IS NULL THEN INSERT INTO `qs_queries` (`created_by`, `query`, `query_normalized`, `is_persisted`, `query_hash`, `result_hash`, `result_number`, `executed`) @@ -49,15 +50,15 @@ BEGIN WHERE NOT EXISTS (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` = @hash); SET queryId = (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` = @hash); END IF; -END $$ +END; $$ + DELIMITER $$ CREATE DEFINER = 'root' PROCEDURE _store_query(IN _username VARCHAR(255), IN query TEXT, IN executed DATETIME, OUT queryId BIGINT) BEGIN DECLARE _queryhash varchar(255) DEFAULT SHA2(query, 256); DECLARE _query TEXT DEFAULT CONCAT('CREATE OR REPLACE TABLE _tmp AS (', query, ')'); - PREPARE stmt FROM _query; EXECUTE stmt; DEALLOCATE PREPARE stmt; CALL hash_table('_tmp', @hash); - SELECT COUNT(*) FROM _tmp INTO @count; + PREPARE stmt FROM _query; EXECUTE stmt; DEALLOCATE PREPARE stmt; CALL hash_table('_tmp', @hash, @count); IF @hash IS NULL THEN INSERT INTO `qs_queries` (`created_by`, `query`, `query_normalized`, `is_persisted`, `query_hash`, `result_hash`, `result_number`, `executed`) @@ -71,5 +72,6 @@ BEGIN WHERE NOT EXISTS (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` = @hash); SET queryId = (SELECT `id` FROM `qs_queries` WHERE `query_hash` = _queryhash AND `result_hash` = @hash); END IF; -END $$ +END; $$ + DELIMITER ; \ No newline at end of file diff --git a/fda-metadata-db/entities/src/main/java/at/tuwien/entities/database/View.java b/fda-metadata-db/entities/src/main/java/at/tuwien/entities/database/View.java index f75494670fbd8f6c1e3b9dced505028d1b9c2507..09e85f03b7267363bfdda8411642351703cdc61a 100644 --- a/fda-metadata-db/entities/src/main/java/at/tuwien/entities/database/View.java +++ b/fda-metadata-db/entities/src/main/java/at/tuwien/entities/database/View.java @@ -1,5 +1,6 @@ package at.tuwien.entities.database; +import at.tuwien.entities.database.table.columns.TableColumn; import at.tuwien.entities.user.User; import lombok.*; import net.sf.jsqlparser.statement.select.FromItem; @@ -11,6 +12,7 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener; import javax.persistence.*; import java.time.Instant; +import java.util.List; @Data @Entity @@ -82,6 +84,21 @@ public class View { return this.getInternalName().equals(name); } + @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.MERGE) + @JoinTable(name = "mdb_view_columns", + joinColumns = { + @JoinColumn(name = "vid", referencedColumnName = "id", insertable = false, updatable = false), + @JoinColumn(name = "vcid", referencedColumnName = "vcid", insertable = false, updatable = false), + @JoinColumn(name = "vdbid", referencedColumnName = "vdbid", insertable = false, updatable = false) + }, + inverseJoinColumns = { + @JoinColumn(name = "cid", referencedColumnName = "id", insertable = false, updatable = false), + @JoinColumn(name = "ctid", referencedColumnName = "tid", insertable = false, updatable = false), + @JoinColumn(name = "cdbid", referencedColumnName = "cdbid", insertable = false, updatable = false) + }) + @OrderColumn(name = "position") + private List<TableColumn> columns; + @Column(nullable = false, updatable = false) @CreatedDate private Instant created; diff --git a/fda-metadata-db/setup-schema.sql b/fda-metadata-db/setup-schema.sql index 60b5f3cf502cdadf42d6f7a6e52177e2fb72c37d..8e45d7012c076f0e130117564ae5386f17efa88e 100644 --- a/fda-metadata-db/setup-schema.sql +++ b/fda-metadata-db/setup-schema.sql @@ -341,7 +341,22 @@ CREATE TABLE IF NOT EXISTS mdb_view created_by bigint NOT NULL, FOREIGN KEY (created_by) REFERENCES mdb_users (UserID), FOREIGN KEY (vdbid) REFERENCES mdb_databases (id), - PRIMARY KEY (id, vdbid) + PRIMARY KEY (id, vcid, vdbid) +) WITH SYSTEM VERSIONING; + +CREATE TABLE mdb_view_columns +( + id BIGINT NOT NULL AUTO_INCREMENT, + cid BIGINT NOT NULL, + ctid BIGINT NOT NULL, + cdbid BIGINT NOT NULL, + vid BIGINT NOT NULL, + vcid BIGINT NOT NULL, + vdbid BIGINT NOT NULL, + position INTEGER NULL, + PRIMARY KEY (id), + FOREIGN KEY (vid, vcid, vdbid) REFERENCES mdb_view (id, vcid, vdbid), + FOREIGN KEY (cid, cdbid, ctid) REFERENCES mdb_columns (ID, cDBID, tID) ) WITH SYSTEM VERSIONING; CREATE TABLE IF NOT EXISTS mdb_identifiers diff --git a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/ExportEndpoint.java b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/ExportEndpoint.java index 263c3ff8629a4d1d1bd191ee57e49632f9baa427..33a9c761a6ebd7d6ffd065362c5db011faf9483f 100644 --- a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/ExportEndpoint.java +++ b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/ExportEndpoint.java @@ -53,7 +53,7 @@ public class ExportEndpoint extends AbstractEndpoint { throw new NotAllowedException("Missing data export permission"); } final HttpHeaders headers = new HttpHeaders(); - final ExportResource resource = queryService.findAll(containerId, databaseId, tableId, timestamp, principal); + final ExportResource resource = queryService.tableFindAll(containerId, databaseId, tableId, timestamp, principal); headers.add("Content-Disposition", "attachment; filename=\"" + resource.getFilename() + "\""); log.trace("export table resulted in resource {}", resource); return ResponseEntity.ok() diff --git a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/QueryEndpoint.java b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/QueryEndpoint.java index 05db9d96edbec6859fe89bd718f38e4bf5dca9da..6f4faa2b4197be398b573ea68f76eddc8c42ab8b 100644 --- a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/QueryEndpoint.java +++ b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/QueryEndpoint.java @@ -108,6 +108,32 @@ public class QueryEndpoint extends AbstractEndpoint { .body(result); } + @GetMapping("/{queryId}/data/count") + @Transactional(readOnly = true) + @Timed(value = "query.reexecute.count", description = "Time needed to re-execute a query") + @Operation(summary = "Re-execute some query", security = @SecurityRequirement(name = "bearerAuth")) + public ResponseEntity<Long> reExecuteCount(@NotNull @PathVariable("id") Long containerId, + @NotNull @PathVariable("databaseId") Long databaseId, + @NotNull @PathVariable("queryId") Long queryId, + Principal principal) + throws QueryStoreException, QueryNotFoundException, DatabaseNotFoundException, ImageNotSupportedException, + QueryMalformedException, TableMalformedException, ColumnParseException, NotAllowedException, + DatabaseConnectionException, UserNotFoundException { + log.debug("endpoint re-execute query count, containerId={}, databaseId={}, queryId={}, principal={}", + containerId, databaseId, queryId, principal); + /* check */ + if (!hasQueryPermission(containerId, databaseId, queryId, "QUERY_RE_EXECUTE", principal)) { + log.error("Missing re-execute query permission"); + throw new NotAllowedException("Missing re-execute query permission"); + } + /* execute */ + final Query query = storeService.findOne(containerId, databaseId, queryId, principal); + final Long result = queryService.reExecuteCount(containerId, databaseId, query, principal); + log.trace("re-execute query count resulted in result {}", result); + return ResponseEntity.status(HttpStatus.ACCEPTED) + .body(result); + } + @GetMapping("/{queryId}/export") @Transactional(readOnly = true) @Timed(value = "query.export", description = "Time needed to export query data") diff --git a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableDataEndpoint.java b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableDataEndpoint.java index a1d1e8648268418e1c0efa75efa2faa8379106a5..277e622b6273f66827b116972b8498e30b20dc43 100644 --- a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableDataEndpoint.java +++ b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/TableDataEndpoint.java @@ -14,7 +14,6 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; @@ -148,7 +147,7 @@ public class TableDataEndpoint extends AbstractEndpoint { @RequestParam(required = false) String sortColumn) throws TableNotFoundException, DatabaseNotFoundException, DatabaseConnectionException, ImageNotSupportedException, TableMalformedException, PaginationException, ContainerNotFoundException, - QueryStoreException, NotAllowedException, QueryMalformedException, SortException, UserNotFoundException { + NotAllowedException, QueryMalformedException, SortException, UserNotFoundException { log.debug("endpoint find table data, containerId={}, databaseId={}, tableId={}, principal={}, timestamp={}, page={}, size={}, sortDirection={}, sortColumn={}", containerId, databaseId, tableId, principal, timestamp, page, size, sortDirection, sortColumn); /* check */ @@ -157,17 +156,35 @@ public class TableDataEndpoint extends AbstractEndpoint { throw new NotAllowedException("Missing data view permission"); } validateDataParams(page, size, sortDirection, sortColumn); - /* find */ - final Long count = queryService.count(containerId, databaseId, tableId, timestamp, principal); - log.debug("find table data has produced {} tuples", count); - final HttpHeaders headers = new HttpHeaders(); - headers.set("FDA-COUNT", count.toString()); - final QueryResultDto response = queryService.findAll(containerId, databaseId, tableId, timestamp, page, size, principal); + final QueryResultDto response = queryService.tableFindAll(containerId, databaseId, tableId, timestamp, page, size, principal); log.trace("find table data resulted in result {}", response); return ResponseEntity.ok() - .headers(headers) .body(response); } + @GetMapping("/count") + @Timed(value = "data.all.count", description = "Time needed to get count of all data from a table") + @Operation(summary = "Find data", security = @SecurityRequirement(name = "bearerAuth")) + public ResponseEntity<Long> getCount(@NotNull @PathVariable("id") Long containerId, + @NotNull @PathVariable("databaseId") Long databaseId, + @NotNull @PathVariable("tableId") Long tableId, + @NotNull Principal principal, + @RequestParam(required = false) Instant timestamp) + throws TableNotFoundException, DatabaseNotFoundException, DatabaseConnectionException, + ImageNotSupportedException, TableMalformedException, PaginationException, ContainerNotFoundException, + QueryStoreException, NotAllowedException, QueryMalformedException, SortException, UserNotFoundException { + log.debug("endpoint find table data, containerId={}, databaseId={}, tableId={}, principal={}, timestamp={}", + containerId, databaseId, tableId, principal, timestamp); + /* check */ + if (!hasTablePermission(containerId, databaseId, tableId, "DATA_VIEW", principal)) { + log.error("Missing data view permission"); + throw new NotAllowedException("Missing data view permission"); + } + /* find */ + final Long count = queryService.tableCount(containerId, databaseId, tableId, timestamp, principal); + log.debug("table data count is {} tuples", count); + return ResponseEntity.ok() + .body(count); + } } diff --git a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/ViewEndpoint.java b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/ViewEndpoint.java index 244280cac6a6a35723526a9aef867d187e1d9a7c..b5b2bf6954025a38f46c79de4b489067324b0945 100644 --- a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/ViewEndpoint.java +++ b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/ViewEndpoint.java @@ -92,7 +92,8 @@ public class ViewEndpoint extends AbstractEndpoint { } final Database database = databaseService.find(containerId, databaseId); log.trace("create view for database {}", database); - final View view = viewService.create(containerId, databaseId, data, principal); + final View view; + view = viewService.create(containerId, databaseId, data, principal); final ViewBriefDto dto = viewMapper.viewToViewBriefDto(view); log.trace("create view resulted in view {}", dto); return ResponseEntity.status(HttpStatus.CREATED) @@ -154,7 +155,7 @@ public class ViewEndpoint extends AbstractEndpoint { @RequestParam(required = false) Long size) throws DatabaseNotFoundException, NotAllowedException, ViewNotFoundException, PaginationException, QueryStoreException, DatabaseConnectionException, TableMalformedException, QueryMalformedException, - ImageNotSupportedException, ColumnParseException, UserNotFoundException, ContainerNotFoundException { + ImageNotSupportedException, ColumnParseException, UserNotFoundException, ContainerNotFoundException, ViewMalformedException { log.debug("endpoint find view data, containerId={}, databaseId={}, viewId={}, principal={}, page={}, size={}", containerId, databaseId, viewId, principal, page, size); /* check */ @@ -167,12 +168,37 @@ public class ViewEndpoint extends AbstractEndpoint { final Database database = databaseService.find(containerId, databaseId); log.trace("find view data for database {}", database); final View view = viewService.findById(databaseId, viewId, principal); - final ExecuteStatementDto statement = ExecuteStatementDto.builder() - .statement(view.getQuery()) - .build(); - log.trace("find view execute statement {}", statement); - final QueryResultDto result = queryService.execute(containerId, databaseId, statement, principal, page, size, - null, null); + final QueryResultDto result = queryService.viewFindAll(containerId, databaseId, view, page, size, principal); + log.trace("execute view {}", view); + log.trace("find view data resulted in result {}", result); + return ResponseEntity.ok() + .body(result); + } + + @GetMapping("/{viewId}/data/count") + @Transactional(readOnly = true) + @Timed(value = "view.data.count", description = "Time needed to retrieve data count from a view") + @Operation(summary = "Find view data count", security = @SecurityRequirement(name = "bearerAuth")) + public ResponseEntity<Long> count(@NotNull @PathVariable("id") Long containerId, + @NotNull @PathVariable("databaseId") Long databaseId, + @NotNull @PathVariable("viewId") Long viewId, + Principal principal) + throws DatabaseNotFoundException, NotAllowedException, ViewNotFoundException, PaginationException, + QueryStoreException, DatabaseConnectionException, TableMalformedException, QueryMalformedException, + ImageNotSupportedException, ColumnParseException, UserNotFoundException, ContainerNotFoundException, ViewMalformedException { + log.debug("endpoint find view data count, containerId={}, databaseId={}, viewId={}, principal={}", + containerId, databaseId, viewId, principal); + /* check */ + if (!hasDatabasePermission(containerId, databaseId, "DATA_VIEW", principal)) { + log.error("Missing view data in view permission"); + throw new NotAllowedException("Missing view data in view permission"); + } + /* find */ + final Database database = databaseService.find(containerId, databaseId); + log.trace("find view data for database {}", database); + final View view = viewService.findById(databaseId, viewId, principal); + final Long result = queryService.viewCount(containerId, databaseId, view, principal); + log.trace("execute view {}", view); log.trace("find view data resulted in result {}", result); return ResponseEntity.ok() .body(result); diff --git a/fda-query-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java b/fda-query-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java index b369dfe7c4a12c8092a4c747a1620ea7d29dc646..556aa3937d3830d8f2c40b6c679ceb874843190b 100644 --- a/fda-query-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java +++ b/fda-query-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java @@ -2411,8 +2411,8 @@ public abstract class BaseUnitTest { .exchangeName(DATABASE_1_EXCHANGE) .creator(USER_1) .owner(USER_1) - .tables(List.of()) /* TABLE_1, TABLE_2, TABLE_3 */ - .views(List.of()) + .tables(List.of(TABLE_1, TABLE_2, TABLE_3)) + .views(List.of(VIEW_4)) .build(); public final static Database DATABASE_2 = Database.builder() diff --git a/fda-query-service/rest-service/src/test/java/at/tuwien/endpoint/ExportEndpointUnitTest.java b/fda-query-service/rest-service/src/test/java/at/tuwien/endpoint/ExportEndpointUnitTest.java index f64fc7af437e5ee1dbd5a7a7398032b371b9cfd4..f5126e08b48fb31b936fd2e56d4ab8ff7bd3f685 100644 --- a/fda-query-service/rest-service/src/test/java/at/tuwien/endpoint/ExportEndpointUnitTest.java +++ b/fda-query-service/rest-service/src/test/java/at/tuwien/endpoint/ExportEndpointUnitTest.java @@ -247,7 +247,7 @@ public class ExportEndpointUnitTest extends BaseUnitTest { } when(tableRepository.find(containerId, databaseId, tableId)) .thenReturn(Optional.of(TABLE_1)); - when(queryService.findAll(containerId, databaseId, tableId, timestamp, principal)) + when(queryService.tableFindAll(containerId, databaseId, tableId, timestamp, principal)) .thenReturn(resource); /* test */ diff --git a/fda-query-service/rest-service/src/test/java/at/tuwien/endpoint/TableDataEndpointUnitTest.java b/fda-query-service/rest-service/src/test/java/at/tuwien/endpoint/TableDataEndpointUnitTest.java index b536d7fe2e3d45589521566b3c6e05a7147f3922..4cf2392ed0a94319043e7738151d0c86123b7cf7 100644 --- a/fda-query-service/rest-service/src/test/java/at/tuwien/endpoint/TableDataEndpointUnitTest.java +++ b/fda-query-service/rest-service/src/test/java/at/tuwien/endpoint/TableDataEndpointUnitTest.java @@ -20,8 +20,10 @@ import at.tuwien.service.TableService; import at.tuwien.service.impl.QueryServiceImpl; import com.rabbitmq.client.Channel; import lombok.extern.log4j.Log4j2; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; @@ -31,6 +33,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import java.security.Principal; import java.time.Instant; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; @@ -72,490 +75,299 @@ public class TableDataEndpointUnitTest extends BaseUnitTest { @Autowired private TableDataEndpoint dataEndpoint; - @Test - public void import_publicAnonymous_fails() { - - /* test */ - assertThrows(NotAllowedException.class, () -> { - generic_import(CONTAINER_1_ID, DATABASE_1_ID, DATABASE_1, TABLE_1_ID, TABLE_1, null, - null, null); - }); - } - - @Test - public void import_publicRead_fails() { - - /* test */ - assertThrows(NotAllowedException.class, () -> { - generic_import(CONTAINER_1_ID, DATABASE_1_ID, DATABASE_1, TABLE_1_ID, TABLE_1, USER_2_USERNAME, - DATABASE_1_READ_ACCESS, USER_2_PRINCIPAL); - }); - } - - @Test - public void import_publicWriteOwn_fails() { - - /* test */ - assertThrows(NotAllowedException.class, () -> { - generic_import(CONTAINER_1_ID, DATABASE_1_ID, DATABASE_1, TABLE_1_ID, TABLE_1, USER_2_USERNAME, - DATABASE_1_WRITE_OWN_ACCESS, USER_2_PRINCIPAL); - }); - } - - @Test - public void import_publicWriteAll_succeeds() throws UserNotFoundException, TableNotFoundException, - NotAllowedException, TableMalformedException, DatabaseConnectionException, QueryMalformedException, - DatabaseNotFoundException, ImageNotSupportedException, ContainerNotFoundException { - - /* test */ - generic_import(CONTAINER_1_ID, DATABASE_1_ID, DATABASE_1, TABLE_1_ID, TABLE_1, USER_2_USERNAME, - DATABASE_1_WRITE_ALL_ACCESS, USER_2_PRINCIPAL); - } - - @Test - public void import_publicOwner_succeds() throws UserNotFoundException, TableNotFoundException, NotAllowedException, - TableMalformedException, DatabaseConnectionException, QueryMalformedException, DatabaseNotFoundException, - ImageNotSupportedException, ContainerNotFoundException { - - /* test */ - generic_import(CONTAINER_1_ID, DATABASE_1_ID, DATABASE_1, TABLE_1_ID, TABLE_1, USER_1_USERNAME, - DATABASE_1_WRITE_ALL_ACCESS, USER_1_PRINCIPAL); - } - - @Test - public void insert_publicAnonymous_fails() { - - /* test */ - assertThrows(NotAllowedException.class, () -> { - generic_insert(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, TABLE_1, USER_2_USERNAME, - null, TABLE_1_CSV_DTO, null); - }); - } - - @Test - public void insert_publicRead_fails() { - - /* test */ - assertThrows(NotAllowedException.class, () -> { - generic_insert(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, TABLE_1, USER_2_USERNAME, - DATABASE_1_READ_ACCESS, TABLE_1_CSV_DTO, USER_2_PRINCIPAL); - }); - } - - @Test - public void insert_publicWriteOwn_fails() { - - /* test */ - assertThrows(NotAllowedException.class, () -> { - generic_insert(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, TABLE_1, USER_2_USERNAME, - DATABASE_1_WRITE_OWN_ACCESS, TABLE_1_CSV_DTO, USER_2_PRINCIPAL); - }); - } - - @Test - public void insert_publicWriteAll_succeeds() throws UserNotFoundException, TableNotFoundException, NotAllowedException, - TableMalformedException, DatabaseConnectionException, DatabaseNotFoundException, ImageNotSupportedException, - ContainerNotFoundException { - - /* test */ - generic_insert(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, TABLE_1, USER_2_USERNAME, - DATABASE_1_WRITE_ALL_ACCESS, TABLE_1_CSV_DTO, USER_2_PRINCIPAL); - } - - @Test - public void insert_publicOwner_succeeds() throws UserNotFoundException, TableNotFoundException, NotAllowedException, - TableMalformedException, DatabaseConnectionException, DatabaseNotFoundException, ImageNotSupportedException, - ContainerNotFoundException { - - /* test */ - generic_insert(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, TABLE_1, USER_1_USERNAME, - DATABASE_1_WRITE_ALL_ACCESS, TABLE_1_CSV_DTO, USER_1_PRINCIPAL); - } - - @Test - public void insert_publicOwnerDataNull_succeeds() throws UserNotFoundException, TableNotFoundException, - NotAllowedException, TableMalformedException, DatabaseConnectionException, DatabaseNotFoundException, - ImageNotSupportedException, ContainerNotFoundException { - - /* test */ - generic_insert(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, TABLE_1, USER_1_USERNAME, - DATABASE_1_WRITE_ALL_ACCESS, null, USER_1_PRINCIPAL); - } - - @Test - public void getAll_publicAnonymous_succeeds() throws TableNotFoundException, DatabaseConnectionException, TableMalformedException, - DatabaseNotFoundException, ImageNotSupportedException, PaginationException, ContainerNotFoundException, - QueryStoreException, NotAllowedException, QueryMalformedException, SortException, UserNotFoundException { - - /* test */ - generic_getAll(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, TABLE_1, null, - null, null, null, null, null, null, null); - } - - @Test - public void getAll_publicRead_succeeds() throws TableNotFoundException, DatabaseConnectionException, TableMalformedException, - DatabaseNotFoundException, ImageNotSupportedException, PaginationException, ContainerNotFoundException, - QueryStoreException, NotAllowedException, QueryMalformedException, SortException, UserNotFoundException { - - /* test */ - generic_getAll(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, TABLE_1, USER_2_USERNAME, - DATABASE_1_READ_ACCESS, USER_2_PRINCIPAL, null, null, null, null, null); - } - - @Test - public void getAll_publicWriteOwn_succeeds() throws TableNotFoundException, DatabaseConnectionException, TableMalformedException, - DatabaseNotFoundException, ImageNotSupportedException, PaginationException, ContainerNotFoundException, - QueryStoreException, NotAllowedException, QueryMalformedException, SortException, UserNotFoundException { - - /* test */ - generic_getAll(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, TABLE_1, USER_2_USERNAME, - DATABASE_1_WRITE_OWN_ACCESS, USER_2_PRINCIPAL, null, null, null, null, null); - } - - @Test - public void getAll_publicWriteAll_succeeds() throws TableNotFoundException, DatabaseConnectionException, TableMalformedException, - DatabaseNotFoundException, ImageNotSupportedException, PaginationException, ContainerNotFoundException, - QueryStoreException, NotAllowedException, QueryMalformedException, SortException, UserNotFoundException { - - /* test */ - generic_getAll(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, TABLE_1, USER_2_USERNAME, - DATABASE_1_WRITE_ALL_ACCESS, USER_2_PRINCIPAL, null, null, null, null, null); - } - - @Test - public void getAll_publicOwner_succeeds() throws TableNotFoundException, DatabaseConnectionException, TableMalformedException, - DatabaseNotFoundException, ImageNotSupportedException, PaginationException, ContainerNotFoundException, - QueryStoreException, NotAllowedException, QueryMalformedException, SortException, UserNotFoundException { - - /* test */ - generic_getAll(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, TABLE_1, USER_1_USERNAME, - DATABASE_1_WRITE_ALL_ACCESS, USER_1_PRINCIPAL, null, null, null, null, null); - } - - @Test - public void getAll_publicAnonymousPageNull_fails() { - final Long page = null; - final Long size = 1L; - - /* test */ - assertThrows(PaginationException.class, () -> { - generic_getAll(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, TABLE_1, null, - null, null, null, page, size, null, null); - }); - } - - @Test - public void getAll_publicAnonymousSizeNull_fails() { - final Long page = 1L; - final Long size = null; - - /* test */ - assertThrows(PaginationException.class, () -> { - generic_getAll(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, TABLE_1, null, - null, null, null, page, size, null, null); - }); - } - - @Test - public void getAll_publicAnonymousPageNegative_fails() { - final Long page = -1L; - final Long size = 1L; - - /* test */ - assertThrows(PaginationException.class, () -> { - generic_getAll(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, TABLE_1, null, - null, null, null, page, size, null, null); - }); - } - - @Test - public void getAll_publicAnonymousSizeZero_fails() { - final Long page = 0L; - final Long size = 0L; - - /* test */ - assertThrows(PaginationException.class, () -> { - generic_getAll(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, TABLE_1, null, - null, null, null, page, size, null, null); - }); - } - - @Test - public void getAll_publicAnonymousSizeNegative_fails() { - final Long page = 0L; - final Long size = -1L; - - /* test */ - assertThrows(PaginationException.class, () -> { - generic_getAll(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, TABLE_1, null, - null, null, null, page, size, null, null); - }); - } - - /* ################################################################################################### */ - /* ## PRIVATE DATABASES ## */ - /* ################################################################################################### */ - - @Test - public void import_privateAnonymous_fails() { - - /* test */ - assertThrows(NotAllowedException.class, () -> { - generic_import(CONTAINER_2_ID, DATABASE_2_ID, DATABASE_2, TABLE_1_ID, TABLE_1, null, null, null); - }); - } - - @Test - public void import_privateRead_fails() { - - /* test */ - assertThrows(NotAllowedException.class, () -> { - generic_import(CONTAINER_2_ID, DATABASE_2_ID, DATABASE_2, TABLE_1_ID, TABLE_1, USER_2_USERNAME, - DATABASE_2_READ_ACCESS, USER_2_PRINCIPAL); - }); - } - - @Test - public void import_privateWriteOwn_fails() { - - /* test */ - assertThrows(NotAllowedException.class, () -> { - generic_import(CONTAINER_2_ID, DATABASE_2_ID, DATABASE_2, TABLE_1_ID, TABLE_1, USER_2_USERNAME, - DATABASE_2_WRITE_OWN_ACCESS, USER_2_PRINCIPAL); - }); - } - - @Test - public void import_privateWriteAll_succeeds() throws UserNotFoundException, TableNotFoundException, - NotAllowedException, TableMalformedException, DatabaseConnectionException, QueryMalformedException, - DatabaseNotFoundException, ImageNotSupportedException, ContainerNotFoundException { - - /* test */ - generic_import(CONTAINER_2_ID, DATABASE_2_ID, DATABASE_2, TABLE_1_ID, TABLE_1, USER_2_USERNAME, - DATABASE_2_WRITE_ALL_ACCESS, USER_2_PRINCIPAL); - } - - @Test - public void import_privateOwner_succeds() throws UserNotFoundException, TableNotFoundException, NotAllowedException, - TableMalformedException, DatabaseConnectionException, QueryMalformedException, DatabaseNotFoundException, - ImageNotSupportedException, ContainerNotFoundException { - - /* test */ - generic_import(CONTAINER_2_ID, DATABASE_2_ID, DATABASE_2, TABLE_1_ID, TABLE_1, USER_1_USERNAME, - DATABASE_2_WRITE_ALL_ACCESS, USER_1_PRINCIPAL); - } - - @Test - public void insert_privateAnonymous_fails() { - - /* test */ - assertThrows(NotAllowedException.class, () -> { - generic_insert(CONTAINER_2_ID, DATABASE_2_ID, TABLE_1_ID, DATABASE_2, TABLE_1, USER_2_USERNAME, - null, TABLE_1_CSV_DTO, null); - }); - } - - @Test - public void insert_privateRead_fails() { - - /* test */ - assertThrows(NotAllowedException.class, () -> { - generic_insert(CONTAINER_2_ID, DATABASE_2_ID, TABLE_1_ID, DATABASE_2, TABLE_1, USER_2_USERNAME, - DATABASE_2_READ_ACCESS, TABLE_1_CSV_DTO, USER_2_PRINCIPAL); + public static Stream<Arguments> import_fails_parameters() { + return Stream.of( + Arguments.arguments("public anonymous", NotAllowedException.class, CONTAINER_1_ID, DATABASE_1_ID, + DATABASE_1, + TABLE_1_ID, TABLE_1, null, null, null), + Arguments.arguments("public read", NotAllowedException.class, CONTAINER_1_ID, DATABASE_1_ID, + DATABASE_1, TABLE_1_ID, + TABLE_1, USER_2_USERNAME, DATABASE_1_READ_ACCESS, USER_2_PRINCIPAL), + Arguments.arguments("public write-own", NotAllowedException.class, CONTAINER_1_ID, DATABASE_1_ID, + DATABASE_1, TABLE_1_ID, + TABLE_1, USER_2_USERNAME, DATABASE_1_WRITE_OWN_ACCESS, USER_2_PRINCIPAL), + Arguments.arguments("private anonymous", NotAllowedException.class, CONTAINER_2_ID, DATABASE_2_ID, + DATABASE_2, TABLE_1_ID, TABLE_1, null, null, null), + Arguments.arguments("private read", NotAllowedException.class, CONTAINER_2_ID, DATABASE_2_ID, + DATABASE_2, TABLE_1_ID, TABLE_1, USER_2_USERNAME, + DATABASE_2_READ_ACCESS, USER_2_PRINCIPAL), + Arguments.arguments("private write-own", NotAllowedException.class, CONTAINER_2_ID, DATABASE_2_ID, + DATABASE_2, TABLE_1_ID, TABLE_1, USER_2_USERNAME, + DATABASE_2_WRITE_OWN_ACCESS, USER_2_PRINCIPAL) + ); + } + + @ParameterizedTest + @MethodSource("import_fails_parameters") + public <T extends Throwable> void import_fails(String test, Class<T> expectedException, Long containerId, + Long databaseId, Database database, Long tableId, Table table, + String username, DatabaseAccess access, Principal principal) { + + /* test */ + assertThrows(expectedException, () -> { + generic_import(containerId, databaseId, database, tableId, table, username, access, principal); }); } - @Test - public void insert_privateWriteOwn_fails() { - - /* test */ - assertThrows(NotAllowedException.class, () -> { - generic_insert(CONTAINER_2_ID, DATABASE_2_ID, TABLE_1_ID, DATABASE_2, TABLE_1, USER_2_USERNAME, - DATABASE_2_WRITE_OWN_ACCESS, TABLE_1_CSV_DTO, USER_2_PRINCIPAL); + public static Stream<Arguments> import_succeeds_parameters() { + return Stream.of( + Arguments.arguments("public write-all", CONTAINER_1_ID, DATABASE_1_ID, DATABASE_1, TABLE_1_ID, TABLE_1, + USER_2_USERNAME, DATABASE_1_WRITE_ALL_ACCESS, USER_2_PRINCIPAL), + Arguments.arguments("public owner", CONTAINER_1_ID, DATABASE_1_ID, DATABASE_1, TABLE_1_ID, TABLE_1, + USER_1_USERNAME, DATABASE_1_WRITE_ALL_ACCESS, USER_1_PRINCIPAL), + Arguments.arguments("private write-all", CONTAINER_2_ID, DATABASE_2_ID, DATABASE_2, TABLE_1_ID, + TABLE_1, USER_2_USERNAME, + DATABASE_2_WRITE_ALL_ACCESS, USER_2_PRINCIPAL), + Arguments.arguments("private owner", CONTAINER_2_ID, DATABASE_2_ID, DATABASE_2, TABLE_1_ID, TABLE_1, + USER_1_USERNAME, + DATABASE_2_WRITE_ALL_ACCESS, USER_1_PRINCIPAL) + ); + } + + @ParameterizedTest + @MethodSource("import_succeeds_parameters") + public void import_succeeds(String test, Long containerId, Long databaseId, Database database, Long tableId, + Table table, String username, DatabaseAccess access, Principal principal) throws UserNotFoundException, TableNotFoundException, NotAllowedException, TableMalformedException, DatabaseConnectionException, QueryMalformedException, DatabaseNotFoundException, ImageNotSupportedException, ContainerNotFoundException { + + /* test */ + generic_import(containerId, databaseId, database, tableId, table, username, access, principal); + } + + public static Stream<Arguments> insert_fails_parameters() { + return Stream.of( + Arguments.arguments("public anonymous", NotAllowedException.class, CONTAINER_1_ID, DATABASE_1_ID, + TABLE_1_ID, + DATABASE_1, TABLE_1, USER_2_USERNAME, null, TABLE_1_CSV_DTO, null), + Arguments.arguments("public read", NotAllowedException.class, CONTAINER_1_ID, DATABASE_1_ID, + TABLE_1_ID, DATABASE_1, + TABLE_1, USER_2_USERNAME, DATABASE_1_READ_ACCESS, TABLE_1_CSV_DTO, USER_2_PRINCIPAL), + Arguments.arguments("public write-own", NotAllowedException.class, CONTAINER_1_ID, DATABASE_1_ID, + TABLE_1_ID, DATABASE_1, + TABLE_1, USER_2_USERNAME, DATABASE_1_WRITE_OWN_ACCESS, TABLE_1_CSV_DTO, USER_2_PRINCIPAL), + Arguments.arguments("private anonymous", NotAllowedException.class, CONTAINER_2_ID, DATABASE_2_ID, + TABLE_1_ID, DATABASE_2, TABLE_1, USER_2_USERNAME, null, + TABLE_1_CSV_DTO, null), + Arguments.arguments("private read", NotAllowedException.class, CONTAINER_2_ID, DATABASE_2_ID, + TABLE_1_ID, DATABASE_2, TABLE_1, USER_2_USERNAME, + DATABASE_2_READ_ACCESS, TABLE_1_CSV_DTO, USER_2_PRINCIPAL), + Arguments.arguments("private write-own", NotAllowedException.class, CONTAINER_2_ID, DATABASE_2_ID, + TABLE_1_ID, DATABASE_2, TABLE_1, USER_2_USERNAME, + DATABASE_2_WRITE_OWN_ACCESS, TABLE_1_CSV_DTO, USER_2_PRINCIPAL) + ); + } + + @ParameterizedTest + @MethodSource("insert_fails_parameters") + public <T extends Throwable> void insert_fails(String test, Class<T> expectedException, Long containerId, + Long databaseId, Long tableId, Database database, Table table, + String username, DatabaseAccess access, TableCsvDto data, + Principal principal) { + + /* test */ + assertThrows(expectedException, () -> { + generic_insert(containerId, databaseId, tableId, database, table, username, access, data, principal); }); } - @Test - public void insert_privateWriteAll_succeeds() throws UserNotFoundException, TableNotFoundException, NotAllowedException, - TableMalformedException, DatabaseConnectionException, DatabaseNotFoundException, ImageNotSupportedException, - ContainerNotFoundException { - - /* test */ - generic_insert(CONTAINER_2_ID, DATABASE_2_ID, TABLE_1_ID, DATABASE_2, TABLE_1, USER_2_USERNAME, - DATABASE_2_WRITE_ALL_ACCESS, TABLE_1_CSV_DTO, USER_2_PRINCIPAL); - } - - @Test - public void insert_privateOwner_succeeds() throws UserNotFoundException, TableNotFoundException, NotAllowedException, - TableMalformedException, DatabaseConnectionException, DatabaseNotFoundException, ImageNotSupportedException, - ContainerNotFoundException { - - /* test */ - generic_insert(CONTAINER_2_ID, DATABASE_2_ID, TABLE_1_ID, DATABASE_2, TABLE_1, USER_1_USERNAME, - DATABASE_2_WRITE_ALL_ACCESS, TABLE_1_CSV_DTO, USER_1_PRINCIPAL); - } - - @Test - public void insert_privateOwnerDataNull_succeeds() throws UserNotFoundException, TableNotFoundException, + public static Stream<Arguments> insert_succeeds_parameters() { + return Stream.of( + Arguments.arguments("public write-all", CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, + TABLE_1, USER_2_USERNAME, + DATABASE_1_WRITE_ALL_ACCESS, TABLE_1_CSV_DTO, USER_2_PRINCIPAL), + Arguments.arguments("public owner", CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, TABLE_1, + USER_1_USERNAME, + DATABASE_1_WRITE_ALL_ACCESS, TABLE_1_CSV_DTO, USER_1_PRINCIPAL), + Arguments.arguments("public owner, data null", CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, + TABLE_1, USER_1_USERNAME, + DATABASE_1_WRITE_ALL_ACCESS, null, USER_1_PRINCIPAL), + Arguments.arguments("private write-all", CONTAINER_2_ID, DATABASE_2_ID, TABLE_1_ID, DATABASE_2, + TABLE_1, USER_2_USERNAME, + DATABASE_2_WRITE_ALL_ACCESS, TABLE_1_CSV_DTO, USER_2_PRINCIPAL), + Arguments.arguments("private owner", CONTAINER_2_ID, DATABASE_2_ID, TABLE_1_ID, DATABASE_2, TABLE_1, + USER_1_USERNAME, + DATABASE_2_WRITE_ALL_ACCESS, TABLE_1_CSV_DTO, USER_1_PRINCIPAL), + Arguments.arguments("private owner, data null", CONTAINER_2_ID, DATABASE_2_ID, TABLE_1_ID, DATABASE_2 + , TABLE_1, USER_1_USERNAME, + DATABASE_2_WRITE_ALL_ACCESS, null, USER_1_PRINCIPAL) + ); + } + + @ParameterizedTest + @MethodSource("insert_succeeds_parameters") + public void insert_succeeds(String test, Long containerId, Long databaseId, Long tableId, Database database, + Table table, String username, DatabaseAccess access, TableCsvDto data, + Principal principal) throws UserNotFoundException, TableNotFoundException, NotAllowedException, TableMalformedException, DatabaseConnectionException, DatabaseNotFoundException, ImageNotSupportedException, ContainerNotFoundException { /* test */ - generic_insert(CONTAINER_2_ID, DATABASE_2_ID, TABLE_1_ID, DATABASE_2, TABLE_1, USER_1_USERNAME, - DATABASE_2_WRITE_ALL_ACCESS, null, USER_1_PRINCIPAL); - } - - @Test - public void getAll_privateAnonymous_succeeds() { - - /* test */ - assertThrows(NotAllowedException.class, () -> { - generic_getAll(CONTAINER_2_ID, DATABASE_2_ID, TABLE_1_ID, DATABASE_2, TABLE_1, null, - null, null, null, null, null, null, null); - }); - } - - @Test - public void getAll_privateRead_succeeds() throws TableNotFoundException, DatabaseConnectionException, TableMalformedException, - DatabaseNotFoundException, ImageNotSupportedException, PaginationException, ContainerNotFoundException, - QueryStoreException, NotAllowedException, QueryMalformedException, SortException, UserNotFoundException { - - /* test */ - generic_getAll(CONTAINER_2_ID, DATABASE_2_ID, TABLE_1_ID, DATABASE_2, TABLE_1, USER_2_USERNAME, - DATABASE_2_READ_ACCESS, USER_2_PRINCIPAL, null, null, null, null, null); - } - - @Test - public void getAll_privateWriteOwn_succeeds() throws TableNotFoundException, DatabaseConnectionException, TableMalformedException, - DatabaseNotFoundException, ImageNotSupportedException, PaginationException, ContainerNotFoundException, - QueryStoreException, NotAllowedException, QueryMalformedException, SortException, UserNotFoundException { - - /* test */ - generic_getAll(CONTAINER_2_ID, DATABASE_2_ID, TABLE_1_ID, DATABASE_2, TABLE_1, USER_2_USERNAME, - DATABASE_2_WRITE_OWN_ACCESS, USER_2_PRINCIPAL, null, null, null, null, null); - } - - @Test - public void getAll_privateWriteAll_succeeds() throws TableNotFoundException, DatabaseConnectionException, TableMalformedException, - DatabaseNotFoundException, ImageNotSupportedException, PaginationException, ContainerNotFoundException, - QueryStoreException, NotAllowedException, QueryMalformedException, SortException, UserNotFoundException { - - /* test */ - generic_getAll(CONTAINER_2_ID, DATABASE_2_ID, TABLE_1_ID, DATABASE_2, TABLE_1, USER_2_USERNAME, - DATABASE_2_WRITE_ALL_ACCESS, USER_2_PRINCIPAL, null, null, null, null, null); - } - - @Test - public void getAll_privateOwner_succeeds() throws TableNotFoundException, DatabaseConnectionException, TableMalformedException, - DatabaseNotFoundException, ImageNotSupportedException, PaginationException, ContainerNotFoundException, - QueryStoreException, NotAllowedException, QueryMalformedException, SortException, UserNotFoundException { - - /* test */ - generic_getAll(CONTAINER_2_ID, DATABASE_2_ID, TABLE_1_ID, DATABASE_2, TABLE_1, USER_1_USERNAME, - DATABASE_2_WRITE_ALL_ACCESS, USER_1_PRINCIPAL, null, null, null, null, null); - } - - @Test - public void getAll_privateReadPageNull_fails() { - final Long page = null; - final Long size = 1L; - - /* test */ - assertThrows(PaginationException.class, () -> { - generic_getAll(CONTAINER_2_ID, DATABASE_2_ID, TABLE_1_ID, DATABASE_2, TABLE_1, USER_2_USERNAME, - DATABASE_2_READ_ACCESS, USER_2_PRINCIPAL, null, page, size, null, null); - }); - } - - @Test - public void getAll_privateReadSizeNull_fails() { - final Long page = 1L; - final Long size = null; - - /* test */ - assertThrows(PaginationException.class, () -> { - generic_getAll(CONTAINER_2_ID, DATABASE_2_ID, TABLE_1_ID, DATABASE_2, TABLE_1, USER_2_USERNAME, - DATABASE_2_READ_ACCESS, USER_2_PRINCIPAL, null, page, size, null, null); - }); - } - - @Test - public void getAll_privateReadPageNegative_fails() { - final Long page = -1L; - final Long size = 1L; - - /* test */ - assertThrows(PaginationException.class, () -> { - generic_getAll(CONTAINER_2_ID, DATABASE_2_ID, TABLE_1_ID, DATABASE_2, TABLE_1, USER_2_USERNAME, - DATABASE_2_READ_ACCESS, USER_2_PRINCIPAL, null, page, size, null, null); + generic_insert(containerId, databaseId, tableId, database, table, username, access, data, principal); + } + + public static Stream<Arguments> getAll_fails_parameters() { + return Stream.of( + Arguments.arguments("public anonymous page null", PaginationException.class, CONTAINER_1_ID, + DATABASE_1_ID, TABLE_1_ID, DATABASE_1, + TABLE_1, null, null, null, null, null, 1L, null, null), + Arguments.arguments("public anonymous size null", PaginationException.class, CONTAINER_1_ID, + DATABASE_1_ID, TABLE_1_ID, DATABASE_1, + TABLE_1, null, null, null, null, 1L, null, null, null), + Arguments.arguments("public anonymous page negative", PaginationException.class, CONTAINER_1_ID, + DATABASE_1_ID, TABLE_1_ID, DATABASE_1, + TABLE_1, null, null, null, null, -1L, 1L, null, null), + Arguments.arguments("public anonymous size zero", PaginationException.class, CONTAINER_1_ID, + DATABASE_1_ID, TABLE_1_ID, DATABASE_1, + TABLE_1, null, null, null, null, 0L, 0L, null, null), + Arguments.arguments("public anonymous size negative", PaginationException.class, CONTAINER_1_ID, + DATABASE_1_ID, TABLE_1_ID, DATABASE_1, + TABLE_1, null, null, null, null, 0L, -1L, null, null), + Arguments.arguments("private anonymous", NotAllowedException.class, CONTAINER_2_ID, DATABASE_2_ID, + TABLE_1_ID, DATABASE_2, TABLE_1, null, null, null, null, + null, null, null, null), + Arguments.arguments("private read, page null", PaginationException.class, CONTAINER_2_ID, + DATABASE_2_ID, TABLE_1_ID, DATABASE_2, TABLE_1, USER_2_USERNAME, + DATABASE_2_READ_ACCESS, USER_2_PRINCIPAL, null, null, 1L, null, null), + Arguments.arguments("private read, size null", PaginationException.class, CONTAINER_2_ID, + DATABASE_2_ID, TABLE_1_ID, DATABASE_2, TABLE_1, USER_2_USERNAME, + DATABASE_2_READ_ACCESS, USER_2_PRINCIPAL, null, 1L, null, null, null), + Arguments.arguments("private read, page negative", PaginationException.class, CONTAINER_2_ID, + DATABASE_2_ID, TABLE_1_ID, DATABASE_2, TABLE_1, USER_2_USERNAME, + DATABASE_2_READ_ACCESS, USER_2_PRINCIPAL, null, -1L, 1L, null, null), + Arguments.arguments("private read, size zero", PaginationException.class, CONTAINER_2_ID, + DATABASE_2_ID, TABLE_1_ID, DATABASE_2, TABLE_1, USER_2_USERNAME, + DATABASE_2_READ_ACCESS, USER_2_PRINCIPAL, null, 0L, 0L, null, null), + Arguments.arguments("private read, size negative", PaginationException.class, CONTAINER_2_ID, + DATABASE_2_ID, TABLE_1_ID, DATABASE_2, TABLE_1, USER_2_USERNAME, + DATABASE_2_READ_ACCESS, USER_2_PRINCIPAL, null, 0L, -1L, null, null) + ); + } + + @ParameterizedTest + @MethodSource("getAll_fails_parameters") + public <T extends Throwable> void getAll_fails(String test, Class<T> expectedException, Long containerId, + Long databaseId, Long tableId, Database database, Table table, + String username, DatabaseAccess access, Principal principal, + Instant timestamp, Long page, Long size, SortType sortDirection, + String sortColumn) { + + /* test */ + assertThrows(expectedException, () -> { + generic_getAll(containerId, databaseId, tableId, database, table, username, access, principal, timestamp, + page, size, sortDirection, sortColumn); }); } - @Test - public void getAll_privateReadSizeZero_fails() { - final Long page = 0L; - final Long size = 0L; - - /* test */ - assertThrows(PaginationException.class, () -> { - generic_getAll(CONTAINER_2_ID, DATABASE_2_ID, TABLE_1_ID, DATABASE_2, TABLE_1, USER_2_USERNAME, - DATABASE_2_READ_ACCESS, USER_2_PRINCIPAL, null, page, size, null, null); - }); + public static Stream<Arguments> getAll_succeeds_parameters() { + return Stream.of( + Arguments.arguments("public anonymous", CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, + TABLE_1, null, null, null, + null, null, null, null, null), + Arguments.arguments("public read", CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, TABLE_1, + USER_2_USERNAME, + DATABASE_1_READ_ACCESS, USER_2_PRINCIPAL, null, null, null, null, null), + Arguments.arguments("public write-own", CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, + TABLE_1, USER_2_USERNAME, + DATABASE_1_WRITE_OWN_ACCESS, USER_2_PRINCIPAL, null, null, null, null, null), + Arguments.arguments("public write-all", CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, + TABLE_1, USER_2_USERNAME, + DATABASE_1_WRITE_ALL_ACCESS, USER_2_PRINCIPAL, null, null, null, null, null), + Arguments.arguments("public owner", CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, TABLE_1, + USER_1_USERNAME, + DATABASE_1_WRITE_ALL_ACCESS, USER_1_PRINCIPAL, null, null, null, null, null), + Arguments.arguments("private read", CONTAINER_2_ID, DATABASE_2_ID, TABLE_1_ID, DATABASE_2, TABLE_1, + USER_2_USERNAME, + DATABASE_2_READ_ACCESS, USER_2_PRINCIPAL, null, null, null, null, null), + Arguments.arguments("private write-own", CONTAINER_2_ID, DATABASE_2_ID, TABLE_1_ID, DATABASE_2, + TABLE_1, USER_2_USERNAME, + DATABASE_2_WRITE_OWN_ACCESS, USER_2_PRINCIPAL, null, null, null, null, null), + Arguments.arguments("private write-all", CONTAINER_2_ID, DATABASE_2_ID, TABLE_1_ID, DATABASE_2, + TABLE_1, USER_2_USERNAME, + DATABASE_2_WRITE_ALL_ACCESS, USER_2_PRINCIPAL, null, null, null, null, null), + Arguments.arguments("private owner", CONTAINER_2_ID, DATABASE_2_ID, TABLE_1_ID, DATABASE_2, TABLE_1, + USER_1_USERNAME, + DATABASE_2_WRITE_ALL_ACCESS, USER_1_PRINCIPAL, null, null, null, null, null) + ); + } + + @ParameterizedTest + @MethodSource("getAll_succeeds_parameters") + public void getAll_succeeds(String test, Long containerId, Long databaseId, Long tableId, Database database, + Table table, String username, DatabaseAccess access, Principal principal, + Instant timestamp, Long page, Long size, SortType sortDirection, String sortColumn) throws UserNotFoundException, TableNotFoundException, QueryStoreException, SortException, TableMalformedException, NotAllowedException, DatabaseConnectionException, QueryMalformedException, DatabaseNotFoundException, ImageNotSupportedException, PaginationException, ContainerNotFoundException { + + /* test */ + generic_getAll(containerId, databaseId, tableId, database, table, username, access, principal, timestamp, + page, size, sortDirection, sortColumn); + } + + public static Stream<Arguments> getCount_succeeds_parameters() { + return Stream.of( + Arguments.arguments("public anonymous", CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, + TABLE_1, null, null, null, null), + Arguments.arguments("public read", CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, TABLE_1, + USER_2_USERNAME, + DATABASE_1_READ_ACCESS, USER_2_PRINCIPAL, null), + Arguments.arguments("public write-own", CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, + TABLE_1, USER_2_USERNAME, + DATABASE_1_WRITE_OWN_ACCESS, USER_2_PRINCIPAL, null), + Arguments.arguments("public write-all", CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, + TABLE_1, USER_2_USERNAME, + DATABASE_1_WRITE_ALL_ACCESS, USER_2_PRINCIPAL, null), + Arguments.arguments("public owner", CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, DATABASE_1, TABLE_1, + USER_1_USERNAME, + DATABASE_1_WRITE_ALL_ACCESS, USER_1_PRINCIPAL, null), + Arguments.arguments("private read", CONTAINER_2_ID, DATABASE_2_ID, TABLE_1_ID, DATABASE_2, TABLE_1, + USER_2_USERNAME, + DATABASE_2_READ_ACCESS, USER_2_PRINCIPAL, null), + Arguments.arguments("private write-own", CONTAINER_2_ID, DATABASE_2_ID, TABLE_1_ID, DATABASE_2, + TABLE_1, USER_2_USERNAME, + DATABASE_2_WRITE_OWN_ACCESS, USER_2_PRINCIPAL, null), + Arguments.arguments("private write-all", CONTAINER_2_ID, DATABASE_2_ID, TABLE_1_ID, DATABASE_2, + TABLE_1, USER_2_USERNAME, + DATABASE_2_WRITE_ALL_ACCESS, USER_2_PRINCIPAL, null), + Arguments.arguments("private owner", CONTAINER_2_ID, DATABASE_2_ID, TABLE_1_ID, DATABASE_2, TABLE_1, + USER_1_USERNAME, + DATABASE_2_WRITE_ALL_ACCESS, USER_1_PRINCIPAL, null) + ); + } + + @ParameterizedTest + @MethodSource("getAll_succeeds_parameters") + public void getCount_succeeds(String test, Long containerId, Long databaseId, Long tableId, Database database, + Table table, String username, DatabaseAccess access, Principal principal, + Instant timestamp) throws UserNotFoundException, TableNotFoundException, QueryStoreException, SortException, TableMalformedException, NotAllowedException, DatabaseConnectionException, QueryMalformedException, DatabaseNotFoundException, ImageNotSupportedException, PaginationException, ContainerNotFoundException { + + /* test */ + generic_getCount(containerId, databaseId, tableId, database, table, username, access, principal, timestamp); } - @Test - public void getAll_privateReadSizeNegative_fails() { - final Long page = 0L; - final Long size = -1L; - - /* test */ - assertThrows(PaginationException.class, () -> { - generic_getAll(CONTAINER_2_ID, DATABASE_2_ID, TABLE_1_ID, DATABASE_2, TABLE_1, USER_2_USERNAME, - DATABASE_2_READ_ACCESS, USER_2_PRINCIPAL, null, page, size, null, null); - }); - } /* ################################################################################################### */ /* ## GENERIC TEST CASES ## */ /* ################################################################################################### */ public void generic_import(Long containerId, Long databaseId, Database database, Long tableId, Table table, - String username, DatabaseAccess access, Principal principal) - throws DatabaseNotFoundException, TableNotFoundException, NotAllowedException, UserNotFoundException, - TableMalformedException, DatabaseConnectionException, QueryMalformedException, ImageNotSupportedException, - ContainerNotFoundException { - final ImportDto request = ImportDto.builder() - .location("test:csv/csv_01.csv") - .build(); + String username, DatabaseAccess access, Principal principal) throws DatabaseNotFoundException, TableNotFoundException, NotAllowedException, UserNotFoundException, TableMalformedException, DatabaseConnectionException, QueryMalformedException, ImageNotSupportedException, ContainerNotFoundException { + final ImportDto request = ImportDto.builder().location("test:csv/csv_01.csv").build(); /* mock */ - when(databaseService.find(containerId, databaseId)) - .thenReturn(database); - when(tableService.find(containerId, databaseId, tableId)) - .thenReturn(table); - when(accessService.find(databaseId, username)) - .thenReturn(access); + when(databaseService.find(containerId, databaseId)).thenReturn(database); + when(tableService.find(containerId, databaseId, tableId)).thenReturn(table); + when(accessService.find(databaseId, username)).thenReturn(access); /* test */ - final ResponseEntity<?> response = dataEndpoint.importCsv(containerId, databaseId, tableId, request, - principal); + final ResponseEntity<?> response = dataEndpoint.importCsv(containerId, databaseId, tableId, request, principal); assertNotNull(response); assertEquals(HttpStatus.ACCEPTED, response.getStatusCode()); } public void generic_insert(Long containerId, Long databaseId, Long tableId, Database database, Table table, - String username, DatabaseAccess access, TableCsvDto data, Principal principal) - throws DatabaseNotFoundException, TableNotFoundException, NotAllowedException, UserNotFoundException, - TableMalformedException, DatabaseConnectionException, ImageNotSupportedException, - ContainerNotFoundException { + String username, DatabaseAccess access, TableCsvDto data, Principal principal) throws DatabaseNotFoundException, TableNotFoundException, NotAllowedException, UserNotFoundException, TableMalformedException, DatabaseConnectionException, ImageNotSupportedException, ContainerNotFoundException { /* mock */ - when(databaseService.find(containerId, databaseId)) - .thenReturn(database); - when(tableService.find(containerId, databaseId, tableId)) - .thenReturn(table); - when(accessService.find(databaseId, username)) - .thenReturn(access); + when(databaseService.find(containerId, databaseId)).thenReturn(database); + when(tableService.find(containerId, databaseId, tableId)).thenReturn(table); + when(accessService.find(databaseId, username)).thenReturn(access); /* test */ final ResponseEntity<?> response = dataEndpoint.insert(containerId, databaseId, tableId, data, principal); @@ -565,23 +377,17 @@ public class TableDataEndpointUnitTest extends BaseUnitTest { public void generic_getAll(Long containerId, Long databaseId, Long tableId, Database database, Table table, String username, DatabaseAccess access, Principal principal, Instant timestamp, - Long page, Long size, SortType sortDirection, String sortColumn) - throws UserNotFoundException, TableMalformedException, NotAllowedException, PaginationException, - TableNotFoundException, QueryStoreException, SortException, DatabaseConnectionException, - QueryMalformedException, DatabaseNotFoundException, ImageNotSupportedException, ContainerNotFoundException { + Long page, Long size, SortType sortDirection, String sortColumn) throws UserNotFoundException, TableMalformedException, NotAllowedException, PaginationException, TableNotFoundException, QueryStoreException, SortException, DatabaseConnectionException, QueryMalformedException, DatabaseNotFoundException, ImageNotSupportedException, ContainerNotFoundException { /* mock */ - when(databaseService.find(containerId, databaseId)) - .thenReturn(database); - when(tableService.find(containerId, databaseId, tableId)) - .thenReturn(table); - when(accessService.find(databaseId, username)) - .thenReturn(access); - when(queryService.findAll(containerId, databaseId, tableId, timestamp, page, size, principal)) - .thenReturn(QUERY_1_RESULT_DTO); + when(databaseService.find(containerId, databaseId)).thenReturn(database); + when(tableService.find(containerId, databaseId, tableId)).thenReturn(table); + when(accessService.find(databaseId, username)).thenReturn(access); + when(queryService.tableFindAll(containerId, databaseId, tableId, timestamp, page, size, principal)).thenReturn(QUERY_1_RESULT_DTO); /* test */ - final ResponseEntity<QueryResultDto> response = dataEndpoint.getAll(containerId, databaseId, tableId, principal, timestamp, page, size, sortDirection, sortColumn); + final ResponseEntity<QueryResultDto> response = dataEndpoint.getAll(containerId, databaseId, tableId, + principal, timestamp, page, size, sortDirection, sortColumn); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getBody()); assertEquals(QUERY_1_RESULT_ID, response.getBody().getId()); @@ -589,4 +395,21 @@ public class TableDataEndpointUnitTest extends BaseUnitTest { assertEquals(QUERY_1_RESULT_RESULT, response.getBody().getResult()); } + public void generic_getCount(Long containerId, Long databaseId, Long tableId, Database database, Table table, + String username, DatabaseAccess access, Principal principal, Instant timestamp) throws UserNotFoundException, TableMalformedException, NotAllowedException, PaginationException, TableNotFoundException, QueryStoreException, SortException, DatabaseConnectionException, QueryMalformedException, DatabaseNotFoundException, ImageNotSupportedException, ContainerNotFoundException { + + /* mock */ + when(databaseService.find(containerId, databaseId)).thenReturn(database); + when(tableService.find(containerId, databaseId, tableId)).thenReturn(table); + when(accessService.find(databaseId, username)).thenReturn(access); + when(queryService.tableCount(containerId, databaseId, tableId, timestamp, principal)).thenReturn(QUERY_1_RESULT_NUMBER); + + /* test */ + final ResponseEntity<Long> response = dataEndpoint.getCount(containerId, databaseId, tableId, + principal, timestamp); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertEquals(QUERY_1_RESULT_NUMBER, response.getBody()); + } + } diff --git a/fda-query-service/rest-service/src/test/java/at/tuwien/endpoint/ViewEndpointUnitTest.java b/fda-query-service/rest-service/src/test/java/at/tuwien/endpoint/ViewEndpointUnitTest.java index 6c57a23e676490e617746ce0ca4bca75d085d119..5cf4aadd98085ace3f9bfe569c5d9e0a063c9103 100644 --- a/fda-query-service/rest-service/src/test/java/at/tuwien/endpoint/ViewEndpointUnitTest.java +++ b/fda-query-service/rest-service/src/test/java/at/tuwien/endpoint/ViewEndpointUnitTest.java @@ -321,7 +321,7 @@ public class ViewEndpointUnitTest extends BaseUnitTest { public void data_publicAnonymous_succeeds() throws UserNotFoundException, NotAllowedException, DatabaseNotFoundException, ViewNotFoundException, DatabaseConnectionException, QueryMalformedException, QueryStoreException, TableMalformedException, ColumnParseException, ImageNotSupportedException, - ContainerNotFoundException, PaginationException { + ContainerNotFoundException, PaginationException, ViewMalformedException { /* test */ data_generic(CONTAINER_1_ID, DATABASE_1_ID, VIEW_1_ID, DATABASE_1, null, null, null); @@ -332,7 +332,7 @@ public class ViewEndpointUnitTest extends BaseUnitTest { public void data_publicAnonymous2_succeeds() throws UserNotFoundException, NotAllowedException, DatabaseNotFoundException, ViewNotFoundException, DatabaseConnectionException, QueryMalformedException, QueryStoreException, TableMalformedException, ColumnParseException, ImageNotSupportedException, - ContainerNotFoundException, PaginationException { + ContainerNotFoundException, PaginationException, ViewMalformedException { /* test */ data_generic(CONTAINER_1_ID, DATABASE_1_ID, VIEW_1_ID, DATABASE_1, null, null, null); @@ -343,7 +343,7 @@ public class ViewEndpointUnitTest extends BaseUnitTest { public void data_publicRead_succeeds() throws UserNotFoundException, NotAllowedException, DatabaseNotFoundException, ViewNotFoundException, DatabaseConnectionException, QueryMalformedException, QueryStoreException, TableMalformedException, ColumnParseException, ImageNotSupportedException, - ContainerNotFoundException, PaginationException { + ContainerNotFoundException, PaginationException, ViewMalformedException { /* test */ data_generic(CONTAINER_1_ID, DATABASE_1_ID, VIEW_1_ID, DATABASE_1, USER_2_USERNAME, USER_2_PRINCIPAL, DATABASE_1_READ_ACCESS); @@ -354,7 +354,7 @@ public class ViewEndpointUnitTest extends BaseUnitTest { public void data_publicWriteOwn_succeeds() throws UserNotFoundException, NotAllowedException, DatabaseNotFoundException, ViewNotFoundException, DatabaseConnectionException, QueryMalformedException, QueryStoreException, TableMalformedException, ColumnParseException, ImageNotSupportedException, - ContainerNotFoundException, PaginationException { + ContainerNotFoundException, PaginationException, ViewMalformedException { /* test */ data_generic(CONTAINER_1_ID, DATABASE_1_ID, VIEW_1_ID, DATABASE_1, USER_2_USERNAME, USER_2_PRINCIPAL, DATABASE_1_WRITE_OWN_ACCESS); @@ -365,7 +365,7 @@ public class ViewEndpointUnitTest extends BaseUnitTest { public void data_publicWriteAll_succeeds() throws UserNotFoundException, NotAllowedException, DatabaseNotFoundException, ViewNotFoundException, DatabaseConnectionException, QueryMalformedException, QueryStoreException, TableMalformedException, ColumnParseException, ImageNotSupportedException, - ContainerNotFoundException, PaginationException { + ContainerNotFoundException, PaginationException, ViewMalformedException { /* test */ data_generic(CONTAINER_1_ID, DATABASE_1_ID, VIEW_1_ID, DATABASE_1, USER_2_USERNAME, USER_2_PRINCIPAL, DATABASE_1_WRITE_ALL_ACCESS); @@ -376,7 +376,7 @@ public class ViewEndpointUnitTest extends BaseUnitTest { public void data_publicOwner_succeeds() throws UserNotFoundException, NotAllowedException, DatabaseNotFoundException, ViewNotFoundException, DatabaseConnectionException, QueryMalformedException, QueryStoreException, TableMalformedException, ColumnParseException, ImageNotSupportedException, - ContainerNotFoundException, PaginationException { + ContainerNotFoundException, PaginationException, ViewMalformedException { /* test */ data_generic(CONTAINER_1_ID, DATABASE_1_ID, VIEW_1_ID, DATABASE_1, USER_1_USERNAME, USER_1_PRINCIPAL, DATABASE_1_WRITE_ALL_ACCESS); @@ -387,7 +387,7 @@ public class ViewEndpointUnitTest extends BaseUnitTest { public void data_privateResearcher_succeeds() throws UserNotFoundException, QueryStoreException, NotAllowedException, DatabaseConnectionException, TableMalformedException, QueryMalformedException, ColumnParseException, DatabaseNotFoundException, ImageNotSupportedException, ContainerNotFoundException, - PaginationException, ViewNotFoundException { + PaginationException, ViewNotFoundException, ViewMalformedException { /* test */ data_generic(CONTAINER_2_ID, DATABASE_2_ID, VIEW_4_ID, DATABASE_2, USER_1_USERNAME, USER_1_PRINCIPAL, null); @@ -570,7 +570,7 @@ public class ViewEndpointUnitTest extends BaseUnitTest { public void data_privateAnonymous_succeeds() throws UserNotFoundException, NotAllowedException, DatabaseNotFoundException, ViewNotFoundException, DatabaseConnectionException, QueryMalformedException, QueryStoreException, TableMalformedException, ColumnParseException, ImageNotSupportedException, - ContainerNotFoundException, PaginationException { + ContainerNotFoundException, PaginationException, ViewMalformedException { /* test */ data_generic(CONTAINER_2_ID, DATABASE_2_ID, VIEW_1_ID, DATABASE_2, null, null, null); @@ -580,7 +580,7 @@ public class ViewEndpointUnitTest extends BaseUnitTest { public void data_privateRead_succeeds() throws UserNotFoundException, NotAllowedException, DatabaseNotFoundException, ViewNotFoundException, DatabaseConnectionException, QueryMalformedException, QueryStoreException, TableMalformedException, ColumnParseException, ImageNotSupportedException, - ContainerNotFoundException, PaginationException { + ContainerNotFoundException, PaginationException, ViewMalformedException { /* test */ data_generic(CONTAINER_2_ID, DATABASE_2_ID, VIEW_1_ID, DATABASE_2, USER_2_USERNAME, USER_2_PRINCIPAL, DATABASE_2_READ_ACCESS); @@ -590,7 +590,7 @@ public class ViewEndpointUnitTest extends BaseUnitTest { public void data_privateWriteOwn_succeeds() throws UserNotFoundException, NotAllowedException, DatabaseNotFoundException, ViewNotFoundException, DatabaseConnectionException, QueryMalformedException, QueryStoreException, TableMalformedException, ColumnParseException, ImageNotSupportedException, - ContainerNotFoundException, PaginationException { + ContainerNotFoundException, PaginationException, ViewMalformedException { /* test */ data_generic(CONTAINER_2_ID, DATABASE_2_ID, VIEW_1_ID, DATABASE_2, USER_2_USERNAME, USER_2_PRINCIPAL, DATABASE_2_WRITE_OWN_ACCESS); @@ -600,7 +600,7 @@ public class ViewEndpointUnitTest extends BaseUnitTest { public void data_privateWriteAll_succeeds() throws UserNotFoundException, NotAllowedException, DatabaseNotFoundException, ViewNotFoundException, DatabaseConnectionException, QueryMalformedException, QueryStoreException, TableMalformedException, ColumnParseException, ImageNotSupportedException, - ContainerNotFoundException, PaginationException { + ContainerNotFoundException, PaginationException, ViewMalformedException { /* test */ data_generic(CONTAINER_2_ID, DATABASE_2_ID, VIEW_1_ID, DATABASE_2, USER_2_USERNAME, USER_2_PRINCIPAL, DATABASE_2_WRITE_ALL_ACCESS); @@ -610,7 +610,7 @@ public class ViewEndpointUnitTest extends BaseUnitTest { public void data_privateOwner_succeeds() throws UserNotFoundException, NotAllowedException, DatabaseNotFoundException, ViewNotFoundException, DatabaseConnectionException, QueryMalformedException, QueryStoreException, TableMalformedException, ColumnParseException, ImageNotSupportedException, - ContainerNotFoundException, PaginationException { + ContainerNotFoundException, PaginationException, ViewMalformedException { /* test */ data_generic(CONTAINER_2_ID, DATABASE_2_ID, VIEW_1_ID, DATABASE_2, USER_1_USERNAME, USER_1_PRINCIPAL, DATABASE_2_WRITE_ALL_ACCESS); @@ -742,7 +742,7 @@ public class ViewEndpointUnitTest extends BaseUnitTest { Principal principal, DatabaseAccess access) throws DatabaseNotFoundException, UserNotFoundException, NotAllowedException, ViewNotFoundException, DatabaseConnectionException, QueryMalformedException, QueryStoreException, TableMalformedException, ColumnParseException, - ImageNotSupportedException, ContainerNotFoundException, PaginationException { + ImageNotSupportedException, ContainerNotFoundException, PaginationException, ViewMalformedException { final ExecuteStatementDto statement = ExecuteStatementDto.builder() .statement(VIEW_1_QUERY) .build(); @@ -763,7 +763,7 @@ public class ViewEndpointUnitTest extends BaseUnitTest { } when(viewService.findById(databaseId, viewId, principal)) .thenReturn(VIEW_1); - when(queryService.execute(containerId, databaseId, statement, principal, page, size, null, null)) + when(queryService.viewFindAll(containerId, databaseId, VIEW_1, page, size, principal)) .thenReturn(QUERY_1_RESULT_DTO); /* test */ diff --git a/fda-query-service/rest-service/src/test/java/at/tuwien/service/QueryServiceIntegrationTest.java b/fda-query-service/rest-service/src/test/java/at/tuwien/service/QueryServiceIntegrationTest.java index f05d8edb5febe0045b7db356b6f8f3e58bf94154..421304d117fab2be463d6fd752a080d52a7171cb 100644 --- a/fda-query-service/rest-service/src/test/java/at/tuwien/service/QueryServiceIntegrationTest.java +++ b/fda-query-service/rest-service/src/test/java/at/tuwien/service/QueryServiceIntegrationTest.java @@ -138,7 +138,7 @@ public class QueryServiceIntegrationTest extends BaseUnitTest { .thenReturn(Optional.of(TABLE_1)); /* test */ - final QueryResultDto result = queryService.findAll(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, Instant.now(), + final QueryResultDto result = queryService.tableFindAll(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, Instant.now(), null, null, USER_1_PRINCIPAL); assertEquals(3, result.getResult().size()); assertEquals(BigInteger.valueOf(1L), result.getResult().get(0).get(COLUMN_1_1_INTERNAL_NAME)); @@ -174,7 +174,7 @@ public class QueryServiceIntegrationTest extends BaseUnitTest { .thenReturn(Optional.of(TABLE_1)); /* test */ - queryService.findAll(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, Instant.now(), page, size, USER_1_PRINCIPAL); + queryService.tableFindAll(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, Instant.now(), page, size, USER_1_PRINCIPAL); } @Test @@ -192,7 +192,7 @@ public class QueryServiceIntegrationTest extends BaseUnitTest { /* test */ assertThrows(TableNotFoundException.class, () -> { - queryService.findAll(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, Instant.now(), page, size, USER_1_PRINCIPAL); + queryService.tableFindAll(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, Instant.now(), page, size, USER_1_PRINCIPAL); }); } @@ -209,7 +209,7 @@ public class QueryServiceIntegrationTest extends BaseUnitTest { /* test */ assertThrows(DatabaseNotFoundException.class, () -> { - queryService.findAll(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, Instant.now(), page, size, USER_1_PRINCIPAL); + queryService.tableFindAll(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, Instant.now(), page, size, USER_1_PRINCIPAL); }); } @@ -287,7 +287,7 @@ public class QueryServiceIntegrationTest extends BaseUnitTest { .thenReturn(Optional.of(TABLE_1)); /* test */ - queryService.findAll(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, null, null, null, USER_1_PRINCIPAL); + queryService.tableFindAll(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, null, null, null, USER_1_PRINCIPAL); } @Test @@ -305,8 +305,8 @@ public class QueryServiceIntegrationTest extends BaseUnitTest { .thenReturn(Optional.of(TABLE_1)); /* test */ - queryService.findAll(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, timestamp, null, null, USER_1_PRINCIPAL); - queryService.findAll(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, timestamp, null, null, USER_1_PRINCIPAL); + queryService.tableFindAll(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, timestamp, null, null, USER_1_PRINCIPAL); + queryService.tableFindAll(CONTAINER_1_ID, DATABASE_1_ID, TABLE_1_ID, timestamp, null, null, USER_1_PRINCIPAL); } @Test diff --git a/fda-query-service/services/src/main/java/at/tuwien/mapper/QueryMapper.java b/fda-query-service/services/src/main/java/at/tuwien/mapper/QueryMapper.java index 6e8d8d5f3577dffb16827161335ae1f2cb82542e..d85aaa7553b825ee12715bc4e8bb2a1176e865e1 100644 --- a/fda-query-service/services/src/main/java/at/tuwien/mapper/QueryMapper.java +++ b/fda-query-service/services/src/main/java/at/tuwien/mapper/QueryMapper.java @@ -6,6 +6,7 @@ import at.tuwien.api.database.table.TableCsvDto; import at.tuwien.api.database.table.TableCsvUpdateDto; import at.tuwien.api.database.table.TableHistoryDto; import at.tuwien.entities.database.Database; +import at.tuwien.entities.database.View; import at.tuwien.entities.database.table.columns.TableColumnType; import at.tuwien.exception.QueryMalformedException; import at.tuwien.exception.QueryStoreException; @@ -573,8 +574,8 @@ public interface QueryMapper { return selection.toString(); } - default PreparedStatement tableToRawCountAllQuery(Connection connection, Table table, Instant timestamp) - throws ImageNotSupportedException, QueryMalformedException { + default String tableToRawCountAllQuery(Table table, Instant timestamp) + throws ImageNotSupportedException { log.trace("mapping table to raw count query, table={}, timestamp={}", table, timestamp); /* check image */ if (!table.getDatabase().getContainer().getImage().getRepository().equals("mariadb")) { @@ -585,25 +586,35 @@ public interface QueryMapper { log.trace("timestamp is null, setting it to now"); timestamp = Instant.now(); } + return columnsToRawCountAllQuery(table.getInternalName(), timestamp); + } + + default String viewToRawCountAllQuery(View view) + throws ImageNotSupportedException { + log.trace("mapping table to raw count query, view={}", view); + /* check image */ + if (!view.getDatabase().getContainer().getImage().getRepository().equals("mariadb")) { + log.error("Currently only MariaDB is supported"); + throw new ImageNotSupportedException("Image not supported."); + } + return columnsToRawCountAllQuery(view.getInternalName(), null); + } + + default String columnsToRawCountAllQuery(String tableName, Instant timestamp) { final StringBuilder statement = new StringBuilder("SELECT COUNT(*) FROM `") - .append(nameToInternalName(table.getName())) - .append("` FOR SYSTEM_TIME AS OF TIMESTAMP '") - .append(LocalDateTime.ofInstant(timestamp, ZoneId.of("UTC"))) - .append("';"); - try { - final PreparedStatement pstmt = connection.prepareStatement(statement.toString()); - log.trace("mapped raw count query {} to prepared statement {}", statement, pstmt); - return pstmt; - } catch (SQLException e) { - log.error("Failed to prepare statement {}, reason: {}", statement, e.getMessage()); - throw new QueryMalformedException("Failed to prepare statement", e); + .append(nameToInternalName(tableName)) + .append("`"); + if(timestamp != null) { + statement.append(" FOR SYSTEM_TIME AS OF TIMESTAMP '") + .append(LocalDateTime.ofInstant(timestamp, ZoneId.of("UTC"))) + .append("'"); } + statement.append(";"); + return statement.toString(); } - default PreparedStatement queryToRawTimestampedQuery(Connection connection, String query, Database database, - Instant timestamp, Boolean selection, Long page, Long size) - throws ImageNotSupportedException, - QueryMalformedException { + default String queryToRawTimestampedQuery(String query, Database database, Instant timestamp, Boolean selection, Long page, Long size) + throws ImageNotSupportedException, QueryMalformedException { log.trace("mapping query to timestamped query, query={}, database={}, timestamp={}, selection={}, page={}, size={}", query, database, timestamp, selection, page, size); /* param check */ @@ -673,17 +684,11 @@ public interface QueryMapper { + LocalDateTime.ofInstant(timestamp, ZoneId.of("UTC")) + "' "); } - try { - log.debug("mapped timestamped query: {}", statement); - return connection.prepareStatement(statement.toString()); - } catch (SQLException e) { - log.error("Failed to prepare statement {}, reason: {}", statement, e.getMessage()); - throw new QueryMalformedException("Failed to prepare statement", e); - } + return statement.toString(); } - default PreparedStatement tableToRawFindAllQuery(Connection connection, Table table, Instant timestamp, Long size, Long page) - throws ImageNotSupportedException, QueryMalformedException { + default String tableToRawFindAllQuery(Table table, Instant timestamp, Long size, Long page) + throws ImageNotSupportedException { log.trace("mapping table to find all query, table={}, timestamp={}, size={}, page={}", table, timestamp, size, page); /* param check */ @@ -697,18 +702,35 @@ public interface QueryMapper { } else { log.trace("timestamp provided {}", timestamp); } + return columnsToRawFindAllQuery(table.getInternalName(), table.getColumns(), timestamp, size, page); + } + + default String viewToRawFindAllQuery(View view, Long size, Long page) + throws ImageNotSupportedException { + log.trace("mapping view to find all query, view={}, size={}, page={}", view, size, page); + /* param check */ + if (!view.getDatabase().getContainer().getImage().getRepository().equals("mariadb")) { + log.error("Currently only MariaDB is supported"); + throw new ImageNotSupportedException("Currently only MariaDB is supported"); + } + return columnsToRawFindAllQuery(view.getInternalName(), view.getColumns(), null, size, page); + } + + private String columnsToRawFindAllQuery(String tableName, List<TableColumn> columns, Instant timestamp, Long size, Long page) { final int[] idx = new int[]{0}; final StringBuilder statement = new StringBuilder("SELECT "); - table.getColumns() - .forEach(column -> statement.append(idx[0]++ > 0 ? "," : "") + columns.forEach(column -> statement.append(idx[0]++ > 0 ? "," : "") .append("`") .append(column.getInternalName()) .append("`")); statement.append(" FROM `") - .append(nameToInternalName(table.getName())) - .append("` FOR SYSTEM_TIME AS OF TIMESTAMP '") - .append(LocalDateTime.ofInstant(timestamp, ZoneId.of("UTC"))) - .append("'"); + .append(nameToInternalName(tableName)) + .append("`"); + if (timestamp != null) { + statement.append(" FOR SYSTEM_TIME AS OF TIMESTAMP '") + .append(LocalDateTime.ofInstant(timestamp, ZoneId.of("UTC"))) + .append("'"); + } if (size != null && page != null) { log.trace("pagination size/limit of {}", size); statement.append(" LIMIT ") @@ -717,34 +739,8 @@ public interface QueryMapper { statement.append(" OFFSET ") .append(page * size) .append(";"); - - } - try { - final PreparedStatement pstmt = connection.prepareStatement(statement.toString()); - log.trace("mapped timestamped query {} to prepared statement {}", statement, pstmt); - return pstmt; - } catch (SQLException e) { - log.error("Failed to prepare statement {}m reason: {}", statement, e.getMessage()); - throw new QueryMalformedException("Failed to prepare statement", e); - } - } - - default QueryResultDto queryTableToQueryResultDto(ResultSet result, Table table) throws DateTimeException, - SQLException { - final List<Map<String, Object>> queryResult = new LinkedList<>(); - while (result.next()) { - /* map the result set to the columns through the stored metadata in the metadata database */ - int[] idx = new int[]{1}; - final Map<String, Object> map = new HashMap<>(); - for (int i = 0; i < table.getColumns().size(); i++) { - map.put(table.getColumns().get(i).getInternalName(), dataColumnToObject(result.getObject(idx[0]++), table.getColumns().get(i))); - } - queryResult.add(map); } - log.trace("mapped result {} to query result {}", result, queryResult); - return QueryResultDto.builder() - .result(queryResult) - .build(); + return statement.toString(); } @Transactional(readOnly = true) diff --git a/fda-query-service/services/src/main/java/at/tuwien/service/QueryService.java b/fda-query-service/services/src/main/java/at/tuwien/service/QueryService.java index 119d887f3cce8a770db719496c51099d9227d3f3..01924d9fb29b8729987e5621bd6d4a4006cfa04d 100644 --- a/fda-query-service/services/src/main/java/at/tuwien/service/QueryService.java +++ b/fda-query-service/services/src/main/java/at/tuwien/service/QueryService.java @@ -5,16 +5,20 @@ import at.tuwien.SortType; import at.tuwien.api.database.query.ExecuteStatementDto; import at.tuwien.api.database.query.ImportDto; import at.tuwien.api.database.query.QueryResultDto; -import at.tuwien.api.database.query.QueryTypeDto; import at.tuwien.api.database.table.TableCsvDeleteDto; import at.tuwien.api.database.table.TableCsvDto; import at.tuwien.api.database.table.TableCsvUpdateDto; +import at.tuwien.entities.database.Database; +import at.tuwien.entities.database.View; +import at.tuwien.entities.database.table.columns.TableColumn; import at.tuwien.exception.*; import at.tuwien.querystore.Query; +import net.sf.jsqlparser.JSQLParserException; import org.springframework.stereotype.Service; import java.security.Principal; import java.time.Instant; +import java.util.List; @Service public interface QueryService { @@ -71,6 +75,26 @@ public interface QueryService { throws QueryMalformedException, DatabaseNotFoundException, ImageNotSupportedException, ColumnParseException, DatabaseConnectionException, TableMalformedException, QueryStoreException, UserNotFoundException; + /** + * Re-Executes the count-statement of an arbitrary query on the database container. We allow the user to only view + * the data, therefore the default "mariadb" user is allowed read-only access "SELECT". + * + * @param containerId The container id. + * @param databaseId The database id. + * @param query The query. + * @param principal The user principal. + * @return The result. + * @throws QueryStoreException The query store is not reachable. + * @throws QueryMalformedException The query is malformed. + * @throws DatabaseNotFoundException The database was not found in the metdata database. + * @throws ImageNotSupportedException The image is not supported. + * @throws TableMalformedException The table is malformed. + * @throws ColumnParseException The column mapping/parsing failed. + */ + Long reExecuteCount(Long containerId, Long databaseId, Query query, Principal principal) + throws QueryMalformedException, DatabaseNotFoundException, ImageNotSupportedException, ColumnParseException, + TableMalformedException, QueryStoreException; + /** * Select all data known in the database-table id tuple at a given time and return a page of specific size, using * Instant to better abstract time concept (JDK 8) from SQL. We use the "mariadb" user for this. @@ -90,8 +114,8 @@ public interface QueryService { * @throws TableMalformedException The table is malformed. * @throws QueryMalformedException The query is malformed. */ - QueryResultDto findAll(Long containerId, Long databaseId, Long tableId, Instant timestamp, - Long page, Long size, Principal principal) throws TableNotFoundException, DatabaseNotFoundException, + QueryResultDto tableFindAll(Long containerId, Long databaseId, Long tableId, Instant timestamp, + Long page, Long size, Principal principal) throws TableNotFoundException, DatabaseNotFoundException, ImageNotSupportedException, DatabaseConnectionException, TableMalformedException, PaginationException, ContainerNotFoundException, QueryMalformedException, UserNotFoundException; @@ -115,11 +139,34 @@ public interface QueryService { * @throws FileStorageException The file could not be exported. * @throws QueryMalformedException The query is malformed. */ - ExportResource findAll(Long containerId, Long databaseId, Long tableId, Instant timestamp, Principal principal) + ExportResource tableFindAll(Long containerId, Long databaseId, Long tableId, Instant timestamp, Principal principal) throws TableNotFoundException, DatabaseNotFoundException, ImageNotSupportedException, DatabaseConnectionException, TableMalformedException, PaginationException, ContainerNotFoundException, FileStorageException, QueryMalformedException, UserNotFoundException; + /** + * Select all data known in the view id tuple and return a page of specific size. + * We use the "mariadb" user for this. + * + * @param containerId The container-database id pair. + * @param databaseId The container-database id pair. + * @param view The view. + * @param page The page. + * @param size The page size. + * @param principal The user principal. + * @return The select all data result + * @throws ViewNotFoundException The view was not found in the metadata database. + * @throws DatabaseNotFoundException The database was not found in the metdata database. + * @throws ImageNotSupportedException The image is not supported. + * @throws ContainerNotFoundException The container was not found in the metadata database. + * @throws ViewMalformedException The table is malformed. + * @throws QueryMalformedException The query is malformed. + */ + QueryResultDto viewFindAll(Long containerId, Long databaseId, View view, + Long page, Long size, Principal principal) throws ViewNotFoundException, DatabaseNotFoundException, + ImageNotSupportedException, DatabaseConnectionException, ViewMalformedException, PaginationException, + ContainerNotFoundException, QueryMalformedException, UserNotFoundException, TableMalformedException; + /** * Finds one query by container-database-query triple. * @@ -156,10 +203,27 @@ public interface QueryService { * @throws TableMalformedException The table columns are messed up what we got from the metadata database. * @throws ImageNotSupportedException The image is not supported. */ - Long count(Long containerId, Long databaseId, Long tableId, Instant timestamp, Principal principal) + Long tableCount(Long containerId, Long databaseId, Long tableId, Instant timestamp, Principal principal) throws ContainerNotFoundException, DatabaseNotFoundException, TableNotFoundException, TableMalformedException, ImageNotSupportedException, DatabaseConnectionException, QueryMalformedException, QueryStoreException, UserNotFoundException; + /** + * Count the total tuples for a given table id within a container-database id tuple at a given time. + * + * @param containerId The container id. + * @param databaseId The database id. + * @param view The view. + * @param principal The user principal. + * @return The number of records, if successful + * @throws ContainerNotFoundException The container was not found in the metadata database. + * @throws DatabaseNotFoundException The database was not found in the remote database. + * @throws TableMalformedException The view columns are messed up what we got from the metadata database. + * @throws ImageNotSupportedException The image is not supported. + */ + Long viewCount(Long containerId, Long databaseId, View view, Principal principal) + throws ContainerNotFoundException, DatabaseNotFoundException, + TableMalformedException, ImageNotSupportedException, DatabaseConnectionException, QueryMalformedException, QueryStoreException, UserNotFoundException; + /** * @param containerId The container id. * @param databaseId The database id. @@ -232,4 +296,14 @@ public interface QueryService { */ void insert(Long containerId, Long databaseId, Long tableId, ImportDto data, Principal principal) throws ImageNotSupportedException, TableMalformedException, DatabaseNotFoundException, TableNotFoundException, ContainerNotFoundException, DatabaseConnectionException, QueryMalformedException, UserNotFoundException; + + /** + * Parses the stored columns from a given query. + * + * @param query The query. + * @param database The database that contains the list of tables with list of columns. + * @return List of columns in the order they are referenced in the query. + * @throws JSQLParserException The columns could not be extracted from the query. + */ + List<TableColumn> parseColumns(String query, Database database) throws JSQLParserException; } diff --git a/fda-query-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java b/fda-query-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java index 2605fb75ff5333b63fb8c898975b7d23225ff960..a1dc0a91b336c9f1f8485ec7a167a6477487291e 100644 --- a/fda-query-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java +++ b/fda-query-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java @@ -39,7 +39,6 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import java.time.DateTimeException; import java.time.Instant; import java.time.format.DateTimeParseException; import java.util.ArrayList; @@ -104,61 +103,147 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService log.error("Failed to map/parse columns: {}", e.getMessage()); throw new ColumnParseException("Failed to map/parse columns: " + e.getMessage(), e); } - final QueryResultDto dto; + final String statement = queryMapper.queryToRawTimestampedQuery(query.getQuery(), database, query.getCreated(), true, page, size); + final QueryResultDto dto = executeNonPersistent(containerId, databaseId, statement, columns); + + dto.setId(query.getId()); + dto.setResultNumber(query.getResultNumber()); + return dto; + } + + @Override + @Transactional(readOnly = true) + public Long reExecuteCount(Long containerId, Long databaseId, Query query, Principal principal) + throws QueryMalformedException, DatabaseNotFoundException, ImageNotSupportedException, ColumnParseException, + TableMalformedException, QueryStoreException { + /* find */ + final Database database = databaseService.find(containerId, databaseId); + if (!database.getContainer().getImage().getRepository().equals("mariadb")) { + throw new ImageNotSupportedException("Currently only MariaDB is supported"); + } + /* run query */ + try { + parseColumns(query.getQuery(), database); + } catch (JSQLParserException e) { + log.error("Failed to map/parse columns: {}", e.getMessage()); + throw new ColumnParseException("Failed to map/parse columns: " + e.getMessage(), e); + } + final String statement = queryMapper.queryToRawTimestampedQuery(query.getQuery(), database, query.getCreated(), false, null, null); + return executeCountNonPersistent(containerId, databaseId, statement); + } + + private PreparedStatement prepareStatement(Connection connection, String statement) throws QueryMalformedException { + try { + final PreparedStatement pstmt = connection.prepareStatement(statement); + log.trace("mapped timestamped query {} to prepared statement {}", statement, pstmt); + return pstmt; + } catch (SQLException e) { + log.error("Failed to prepare statement {}m reason: {}", statement, e.getMessage()); + throw new QueryMalformedException("Failed to prepare statement", e); + } + } + + private QueryResultDto executeNonPersistent(Long containerId, Long databaseId, String statement, List<TableColumn> columns) + throws QueryMalformedException, DatabaseNotFoundException, TableMalformedException { + /* find */ + final Database database = databaseService.find(containerId, databaseId); + final User root = databaseMapper.containerToPrivilegedUser(database.getContainer()); + /* run query */ + final ComboPooledDataSource dataSource = getDataSource(database.getContainer().getImage(), + database.getContainer(), database, root); try { final Connection connection = dataSource.getConnection(); - final PreparedStatement preparedStatement = queryMapper.queryToRawTimestampedQuery(connection, query.getQuery(), - database, query.getCreated(), true, page, size); + final PreparedStatement preparedStatement = prepareStatement(connection, statement); final ResultSet resultSet = preparedStatement.executeQuery(); - dto = queryMapper.resultListToQueryResultDto(columns, resultSet); + return queryMapper.resultListToQueryResultDto(columns, resultSet); } catch (SQLException e) { log.error("Failed to execute and map time-versioned query: {}", e.getMessage()); throw new TableMalformedException("Failed to execute and map time-versioned query: " + e.getMessage(), e); } finally { dataSource.close(); } - dto.setId(query.getId()); - dto.setResultNumber(query.getResultNumber()); - return dto; } - @Override - @Transactional(readOnly = true) - public QueryResultDto findAll(Long containerId, Long databaseId, Long tableId, Instant timestamp, Long page, - Long size, Principal principal) throws TableNotFoundException, DatabaseNotFoundException, - ImageNotSupportedException, DatabaseConnectionException, TableMalformedException, PaginationException, - ContainerNotFoundException, QueryMalformedException, UserNotFoundException { + private Long executeCountNonPersistent(Long containerId, Long databaseId, String statement) + throws QueryMalformedException, TableMalformedException, DatabaseNotFoundException, QueryStoreException { /* find */ final Database database = databaseService.find(containerId, databaseId); - final Table table = tableService.find(containerId, databaseId, tableId); final User root = databaseMapper.containerToPrivilegedUser(database.getContainer()); /* run query */ final ComboPooledDataSource dataSource = getDataSource(database.getContainer().getImage(), database.getContainer(), database, root); - final QueryResultDto result; try { final Connection connection = dataSource.getConnection(); - final PreparedStatement preparedStatement = queryMapper.tableToRawFindAllQuery(connection, table, timestamp, size, page); + final PreparedStatement preparedStatement = prepareStatement(connection, statement); final ResultSet resultSet = preparedStatement.executeQuery(); - result = queryMapper.queryTableToQueryResultDto(resultSet, table); - } catch (DateTimeException e) { - log.error("Failed to parse date from the one stored in the metadata database: {}", e.getMessage()); - throw new TableMalformedException("Could not parse date from format: " + e.getMessage(), e); + return queryMapper.resultSetToNumber(resultSet); } catch (SQLException e) { log.error("Failed to map object: {}", e.getMessage()); throw new TableMalformedException("Failed to map object: " + e.getMessage(), e); } finally { dataSource.close(); } - return result; } @Override @Transactional(readOnly = true) - public ExportResource findAll(Long containerId, Long databaseId, Long tableId, Instant timestamp, Principal principal) - throws TableNotFoundException, DatabaseNotFoundException, ImageNotSupportedException, - DatabaseConnectionException, TableMalformedException, PaginationException, ContainerNotFoundException, - FileStorageException, QueryMalformedException, UserNotFoundException { + public QueryResultDto tableFindAll(Long containerId, Long databaseId, Long tableId, Instant timestamp, Long page, + Long size, Principal principal) throws TableNotFoundException, DatabaseNotFoundException, + ImageNotSupportedException, TableMalformedException, QueryMalformedException { + /* find */ + final Table table = tableService.find(containerId, databaseId, tableId); + /* run query */ + String statement = queryMapper.tableToRawFindAllQuery(table, timestamp, size, page); + return executeNonPersistent(containerId, databaseId, statement, table.getColumns()); + } + + @Override + @Transactional(readOnly = true) + public QueryResultDto viewFindAll(Long containerId, Long databaseId, View view, + Long page, Long size, Principal principal) throws DatabaseNotFoundException, + ImageNotSupportedException, QueryMalformedException, TableMalformedException { + /* find */ + /* run query */ + String statement = queryMapper.viewToRawFindAllQuery(view, size, page); + return executeNonPersistent(containerId, databaseId, statement, view.getColumns()); + } + + @Override + @Transactional + public Long tableCount(Long containerId, Long databaseId, Long tableId, Instant timestamp, Principal principal) + throws DatabaseNotFoundException, TableNotFoundException, ImageNotSupportedException, + QueryMalformedException, QueryStoreException, TableMalformedException { + /* find */ + final Table table = tableService.find(containerId, databaseId, tableId); + final String statement = queryMapper.tableToRawCountAllQuery(table, timestamp); + return executeCountNonPersistent(containerId, databaseId, statement); + } + + @Override + @Transactional + public Long viewCount(Long containerId, Long databaseId, View view, Principal principal) + throws DatabaseNotFoundException, ImageNotSupportedException, + QueryMalformedException, QueryStoreException, TableMalformedException { + /* find */ + final String statement = queryMapper.viewToRawCountAllQuery(view); + return executeCountNonPersistent(containerId, databaseId, statement); + } + + @Transactional(readOnly = true) + public QueryResultDto findAllView(Long containerId, Long databaseId, Long viewId, Instant timestamp, Long page, + Long size, Principal principal) throws TableNotFoundException, DatabaseNotFoundException, + ImageNotSupportedException, TableMalformedException, QueryMalformedException { + /* find */ + final Table table = tableService.find(containerId, databaseId, viewId); + /* run query */ + String statement = queryMapper.tableToRawFindAllQuery(table, timestamp, size, page); + return executeNonPersistent(containerId, databaseId, statement, table.getColumns()); + } + + @Override + @Transactional(readOnly = true) + public ExportResource tableFindAll(Long containerId, Long databaseId, Long tableId, Instant timestamp, Principal principal) + throws TableNotFoundException, DatabaseNotFoundException, FileStorageException, QueryMalformedException { final String filename = RandomStringUtils.randomAlphabetic(40) + ".csv"; /* find */ final Database database = databaseService.find(containerId, databaseId); @@ -227,36 +312,11 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService .build(); } - @Override - @Transactional - public Long count(Long containerId, Long databaseId, Long tableId, Instant timestamp, Principal principal) - throws DatabaseNotFoundException, TableNotFoundException, ImageNotSupportedException, - QueryMalformedException, QueryStoreException, TableMalformedException { - /* find */ - final Database database = databaseService.find(containerId, databaseId); - final Table table = tableService.find(containerId, databaseId, tableId); - final User root = databaseMapper.containerToPrivilegedUser(database.getContainer()); - /* run query */ - final ComboPooledDataSource dataSource = getDataSource(database.getContainer().getImage(), - database.getContainer(), database, root); - try { - final Connection connection = dataSource.getConnection(); - final PreparedStatement preparedStatement = queryMapper.tableToRawCountAllQuery(connection, table, timestamp); - final ResultSet resultSet = preparedStatement.executeQuery(); - return queryMapper.resultSetToNumber(resultSet); - } catch (SQLException e) { - log.error("Failed to count raw tuples: {}", e.getMessage()); - throw new TableMalformedException("Failed to count raw tuples: " + e.getMessage(), e); - } finally { - dataSource.close(); - } - } - @Override @Transactional public void update(Long containerId, Long databaseId, Long tableId, TableCsvUpdateDto data, Principal principal) throws ImageNotSupportedException, TableMalformedException, DatabaseNotFoundException, - TableNotFoundException, QueryMalformedException, UserNotFoundException { + TableNotFoundException, QueryMalformedException { /* find */ final Database database = databaseService.find(containerId, databaseId); final Table table = tableService.find(containerId, databaseId, tableId); @@ -395,7 +455,7 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService * @throws JSQLParserException The columns could not be extracted from the query. */ @Transactional(readOnly = true) - protected List<TableColumn> parseColumns(String query, Database database) throws JSQLParserException { + public List<TableColumn> parseColumns(String query, Database database) throws JSQLParserException { final List<TableColumn> columns = new ArrayList<>(); final CCJSqlParserManager parserRealSql = new CCJSqlParserManager(); final Statement statement = parserRealSql.parse(new StringReader(query)); diff --git a/fda-query-service/services/src/main/java/at/tuwien/service/impl/ViewServiceImpl.java b/fda-query-service/services/src/main/java/at/tuwien/service/impl/ViewServiceImpl.java index 830cc6d216c6ded0d7ba54cc1a2eec61203549e5..4a8e6b8408642cfefe968c1c4fc341fc6de5469b 100644 --- a/fda-query-service/services/src/main/java/at/tuwien/service/impl/ViewServiceImpl.java +++ b/fda-query-service/services/src/main/java/at/tuwien/service/impl/ViewServiceImpl.java @@ -3,6 +3,7 @@ package at.tuwien.service.impl; import at.tuwien.api.database.ViewCreateDto; import at.tuwien.entities.database.Database; import at.tuwien.entities.database.View; +import at.tuwien.entities.database.table.columns.TableColumn; import at.tuwien.entities.user.User; import at.tuwien.exception.*; import at.tuwien.mapper.DatabaseMapper; @@ -10,10 +11,12 @@ import at.tuwien.mapper.ViewMapper; import at.tuwien.repository.elastic.ViewIdxRepository; import at.tuwien.repository.jpa.ViewRepository; import at.tuwien.service.DatabaseService; +import at.tuwien.service.QueryService; import at.tuwien.service.UserService; import at.tuwien.service.ViewService; import com.mchange.v2.c3p0.ComboPooledDataSource; import lombok.extern.log4j.Log4j2; +import net.sf.jsqlparser.JSQLParserException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -35,17 +38,19 @@ public class ViewServiceImpl extends HibernateConnector implements ViewService { private final ViewRepository viewRepository; private final DatabaseService databaseService; private final ViewIdxRepository viewIdxRepository; + private final QueryService queryService; @Autowired public ViewServiceImpl(ViewMapper viewMapper, UserService userService, DatabaseMapper databaseMapper, ViewRepository viewRepository, DatabaseService databaseService, - ViewIdxRepository viewIdxRepository) { + ViewIdxRepository viewIdxRepository, QueryService queryService) { this.viewMapper = viewMapper; this.userService = userService; this.databaseMapper = databaseMapper; this.viewRepository = viewRepository; this.databaseService = databaseService; this.viewIdxRepository = viewIdxRepository; + this.queryService = queryService; } @Override @@ -118,6 +123,13 @@ public class ViewServiceImpl extends HibernateConnector implements ViewService { /* create view */ final ComboPooledDataSource dataSource = getDataSource(database.getContainer().getImage(), database.getContainer(), database, root); + final List<TableColumn> columns; + try { + columns = queryService.parseColumns(data.getQuery(), database); + } catch (JSQLParserException e) { + log.error("Failed to map/parse columns: {}", e.getMessage()); + throw new QueryMalformedException(e.getMessage(), e); + } try { final Connection connection = dataSource.getConnection(); final PreparedStatement createViewStatement = viewMapper.viewCreateDtoToRawCreateViewQuery(connection, data); @@ -139,6 +151,7 @@ public class ViewServiceImpl extends HibernateConnector implements ViewService { .query(data.getQuery()) .isInitialView(false) .isPublic(data.getIsPublic()) + .columns(columns) .build(); final View view = viewRepository.save(entity); log.info("Created view with id {}", view.getId()); diff --git a/fda-ui/components/DatabaseList.vue b/fda-ui/components/DatabaseList.vue index 98e4051b639e52addcb3cb08f77cde0b1868b491..2f85f6a72ed63ff7c776abf354e839b5fc4198c9 100644 --- a/fda-ui/components/DatabaseList.vue +++ b/fda-ui/components/DatabaseList.vue @@ -37,6 +37,7 @@ Start </v-btn> </v-card-text> + <v-divider v-if="idx - 1 === databases.length" class="mx-4" /> </v-card> <v-toolbar v-if="false" flat> <v-toolbar-title> diff --git a/fda-ui/components/query/Builder.vue b/fda-ui/components/query/Builder.vue index b055f9daa53118c4d738f8154499a66d33848fc7..311aac14f2dce772d544e29c11b558dab57e9e9e 100644 --- a/fda-ui/components/query/Builder.vue +++ b/fda-ui/components/query/Builder.vue @@ -330,7 +330,7 @@ export default { this.$toast.error(err.response.data.message) } this.loadingQuery = false - await this.$refs.queryResults.reExecute(this.resultId) + await Promise.all([this.$refs.queryResults.reExecute(this.resultId), this.$refs.queryResults.reExecuteCount(this.resultId)]) }, async buildQuery () { if (!this.table) { diff --git a/fda-ui/components/query/Results.vue b/fda-ui/components/query/Results.vue index 4d52923b58edcb2e02d63c055ee77dea183b5195..d04b59378614effed3b3a6595bbb912f13c164bc 100644 --- a/fda-ui/components/query/Results.vue +++ b/fda-ui/components/query/Results.vue @@ -3,7 +3,7 @@ flat :headers="result.headers" :items="result.rows" - :loading="loading" + :loading="loading > 0" :options.sync="options" :server-items-length="total" /> </template> @@ -18,7 +18,7 @@ export default { }, data () { return { - loading: false, + loading: 0, resultId: null, id: null, result: { @@ -29,7 +29,7 @@ export default { page: 1, itemsPerPage: 10 }, - total: 0 + total: -1 } }, computed: { @@ -60,7 +60,7 @@ export default { }, methods: { async executeFirstTime (parent, sql, timestamp) { - this.loading = true + this.loading++ try { const res = await this.$axios.post(this.executeUrl, { statement: sql, timestamp }, this.config) console.debug('query result', res.data) @@ -80,7 +80,7 @@ export default { } this.error = true } - this.loading = false + this.loading-- }, buildHeaders (firstLine) { return Object.keys(firstLine).map(k => ({ @@ -92,13 +92,16 @@ export default { reExecuteUrl (resultId) { const page = this.options.page - 1 const urlParams = `page=${page}&size=${this.options.itemsPerPage}` - return `/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}` + (this.type === 'view' ? '/view' : '/query') + `/${resultId}/data?${urlParams}` + return `/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/${this.type}/${resultId}/data?${urlParams}` + }, + reExecuteCountUrl (resultId) { + return `/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/${this.type}/${resultId}/data/count` }, async reExecute (id) { if (id === null) { return } - this.loading = true + this.loading++ try { const res = await this.$axios.get(this.reExecuteUrl(id), this.config) this.mapResults(res.data) @@ -107,7 +110,21 @@ export default { console.error('failed to execute query', error) this.error = true } - this.loading = false + this.loading-- + }, + async reExecuteCount (id) { + if (id === null) { + return + } + this.loading++ + try { + const res = await this.$axios.get(this.reExecuteCountUrl(id), this.config) + this.total = res.data + } catch (error) { + console.error('failed to execute query count', error) + this.error = true + } + this.loading-- }, mapResults (data) { if (data.result.length) { @@ -115,7 +132,9 @@ export default { } console.debug('query result', data) this.result.rows = data.result - this.total = data.result_number + if (this.total < 0 && data.result_number != null) { + this.total = data.result_number + } } } } diff --git a/fda-ui/pages/container/_container_id/database/_database_id/query/_query_id/index.vue b/fda-ui/pages/container/_container_id/database/_database_id/query/_query_id/index.vue index 4a42f9d4a898e3a7cb5c332c8a11860b2a009ba2..4ca589d1527db7c2bd3eea1a845348c3f3d2d9c9 100644 --- a/fda-ui/pages/container/_container_id/database/_database_id/query/_query_id/index.vue +++ b/fda-ui/pages/container/_container_id/database/_database_id/query/_query_id/index.vue @@ -370,6 +370,7 @@ export default { methods: { loadResult () { this.$refs.queryResults.reExecute(this.query.id) + this.$refs.queryResults.reExecuteCount(this.query.id) }, async download (mime) { if (mime === 'text/csv') { diff --git a/fda-ui/pages/container/_container_id/database/_database_id/table/_table_id/data.vue b/fda-ui/pages/container/_container_id/database/_database_id/table/_table_id/data.vue index 69931d8038a2052ab5548c6c51027429a176c154..17f58d3b1ed4fea81a5bb3f5aee217b26b2df852 100644 --- a/fda-ui/pages/container/_container_id/database/_database_id/table/_table_id/data.vue +++ b/fda-ui/pages/container/_container_id/database/_database_id/table/_table_id/data.vue @@ -23,7 +23,7 @@ </v-toolbar-title> </v-toolbar> <v-card tile> - <v-progress-linear v-if="loadingData || error" :value="loadProgress" :color="error ? 'error' : 'primary'" /> + <v-progress-linear v-if="loadingData > 0 || error" :value="loadProgress" :color="error ? 'error' : 'primary'" /> <v-data-table :headers="headers" :items="rows" @@ -51,10 +51,10 @@ export default { data () { return { loading: true, - loadingData: true, + loadingData: 0, loadProgress: 0, editTupleDialog: false, - total: 0, + total: -1, footerProps: { 'items-per-page-options': [10, 20, 30, 40, 50] }, @@ -64,6 +64,7 @@ export default { selection: [], pickVersionDialog: null, version: null, + lastReload: new Date(), tab: null, error: false, // XXX: `error` is never changed options: { @@ -165,7 +166,7 @@ export default { watch: { version (newVersion, oldVersion) { console.info('selected new version', newVersion) - this.loadData() + this.reload() }, options () { this.loadData() @@ -177,7 +178,7 @@ export default { } }, mounted () { - this.loadData() + this.reload() this.simulateProgress() this.loadProperties() }, @@ -254,19 +255,22 @@ export default { this.selection = [] } if (success) { - this.loadData() + this.reload() } }, + reload () { + this.lastReload = new Date() + this.loadData() + this.loadCount() + }, async loadData () { try { - this.loadingData = true - let url = `/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/table/${this.$route.params.table_id}/data?page=${this.options.page - 1}&size=${this.options.itemsPerPage}` + this.loadingData++ + const url = `/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/table/${this.$route.params.table_id}/data?page=${this.options.page - 1}&size=${this.options.itemsPerPage}×tamp=${this.versionISO || this.lastReload.toISOString()}` if (this.version !== null) { console.info('versioning active', this.version) - url += `×tamp=${this.versionISO}` } const res = await this.$axios.get(url, this.config) - this.total = parseInt(res.headers['fda-count']) this.rows = res.data.result.map((row) => { for (const col in row) { const columnDefinition = this.dateColumns.filter(c => c.internal_name === col) @@ -294,8 +298,36 @@ export default { console.error('Failed to load data', code) this.$toast.error('Failed to load data: ' + message) } + } finally { + this.loadingData-- + } + }, + async loadCount () { + try { + this.loadingData++ + const url = `/api/container/${this.$route.params.container_id}/database/${this.$route.params.database_id}/table/${this.$route.params.table_id}/data/count?timestamp=${this.versionISO || this.lastReload.toISOString()}` + if (this.version !== null) { + console.info('versioning active', this.version) + } + const res = await this.$axios.get(url, this.config) + this.total = res.data + console.info('total', this.total) + } catch (error) { + console.error('Failed to load count', error) + this.error = true + this.loadProgress = 100 + const { status, data } = error.response + const { message, code } = data + if (status === 423) { + console.error('Database is offline', code) + this.$toast.error('Database is offline: ' + message) + } else { + console.error('Failed to load data', code) + this.$toast.error('Failed to load data: ' + message) + } + } finally { + this.loadingData-- } - this.loadingData = false }, simulateProgress () { if (this.loadProgress !== 0) { diff --git a/fda-ui/pages/container/_container_id/database/_database_id/view/_view_id/index.vue b/fda-ui/pages/container/_container_id/database/_database_id/view/_view_id/index.vue index 4d9df5a1e7658661fccfd4d2ffea501b54c0fdec..55930ae93e6856d48598213f02e4ded25c69ba24 100644 --- a/fda-ui/pages/container/_container_id/database/_database_id/view/_view_id/index.vue +++ b/fda-ui/pages/container/_container_id/database/_database_id/view/_view_id/index.vue @@ -191,6 +191,7 @@ export default { return } this.$refs.queryResults.reExecute(viewId) + this.$refs.queryResults.reExecuteCount(viewId) }, formatUTC (timestamp) { return formatTimestampUTCLabel(timestamp)