From 49c41d420ea479b6a09d6864b69529b2648e629f Mon Sep 17 00:00:00 2001 From: Martin Weise <martin.weise@tuwien.ac.at> Date: Wed, 22 Jan 2025 10:52:40 +0100 Subject: [PATCH] It builds somehow Signed-off-by: Martin Weise <martin.weise@tuwien.ac.at> --- dbrepo-analyse-service/Pipfile.lock | 6 +- .../lib/dbrepo-1.6.2.tar.gz | Bin 40056 -> 40094 bytes .../at/tuwien/endpoints/AccessEndpoint.java | 18 +- .../at/tuwien/endpoints/DatabaseEndpoint.java | 32 +- ...bstractEndpoint.java => RestEndpoint.java} | 2 +- .../at/tuwien/endpoints/SubsetEndpoint.java | 25 +- .../at/tuwien/endpoints/TableEndpoint.java | 58 +- .../at/tuwien/endpoints/ViewEndpoint.java | 41 +- .../tuwien/validation/EndpointValidator.java | 14 +- .../java/at/tuwien/config/MariaDbConfig.java | 223 +----- .../endpoint/AccessEndpointUnitTest.java | 36 +- .../endpoint/DatabaseEndpointUnitTest.java | 60 +- .../endpoint/SubsetEndpointUnitTest.java | 178 ++--- .../endpoint/TableEndpointUnitTest.java | 195 ++--- .../tuwien/endpoint/ViewEndpointUnitTest.java | 62 +- .../MetadataServiceGatewayUnitTest.java | 129 +--- .../DefaultListenerIntegrationTest.java | 6 +- .../listener/DefaultListenerUnitTest.java | 6 +- .../at/tuwien/mvc/SubsetEndpointMvcTest.java | 12 +- .../service/AccessServiceIntegrationTest.java | 36 +- .../ContainerServiceIntegrationTest.java | 109 +++ .../service/CredentialServiceUnitTest.java | 96 +-- .../DatabaseServiceIntegrationTest.java | 676 +++++++++++++++++- .../service/QueueServiceIntegrationTest.java | 14 +- .../service/SchemaServiceIntegrationTest.java | 416 ----------- .../service/SubsetServiceIntegrationTest.java | 56 +- .../service/TableServiceIntegrationTest.java | 377 ++-------- .../service/ViewServiceIntegrationTest.java | 45 +- .../java/at/tuwien/config/CacheConfig.java | 30 +- .../gateway/MetadataServiceGateway.java | 31 +- .../impl/MetadataServiceGatewayImpl.java | 120 ++-- .../at/tuwien/listener/DefaultListener.java | 8 +- .../java/at/tuwien/mapper/MariaDbMapper.java | 12 +- .../java/at/tuwien/mapper/MetadataMapper.java | 28 +- .../java/at/tuwien/service/AccessService.java | 11 +- .../at/tuwien/service/ContainerService.java | 33 + .../at/tuwien/service/CredentialService.java | 20 +- .../at/tuwien/service/DatabaseService.java | 85 ++- .../java/at/tuwien/service/QueueService.java | 4 +- .../java/at/tuwien/service/SchemaService.java | 33 - .../java/at/tuwien/service/SubsetService.java | 36 +- .../java/at/tuwien/service/TableService.java | 64 +- .../java/at/tuwien/service/ViewService.java | 50 +- .../impl/AccessServiceMariaDbImpl.java | 20 +- .../impl/ContainerServiceMariaDbImpl.java | 103 +++ .../service/impl/CredentialServiceImpl.java | 60 +- .../at/tuwien/service/impl/DataConnector.java | 66 ++ .../impl/DatabaseServiceMariaDbImpl.java | 341 ++++++++- .../service/impl/HibernateConnector.java | 53 -- .../impl/QueueServiceRabbitMqImpl.java | 10 +- .../impl/SchemaServiceMariaDbImpl.java | 164 ----- .../impl/SubsetServiceMariaDbImpl.java | 88 +-- .../service/impl/TableServiceMariaDbImpl.java | 169 ++--- .../service/impl/ViewServiceMariaDbImpl.java | 168 +---- .../main/java/at/tuwien/api/CacheableDto.java | 45 ++ .../at/tuwien/api/PrivilegedObjectDto.java | 18 - .../at/tuwien/api/container/ContainerDto.java | 25 +- .../internal/PrivilegedContainerDto.java | 60 -- .../tuwien/api/database/DatabaseBriefDto.java | 3 + .../at/tuwien/api/database/DatabaseDto.java | 52 +- .../java/at/tuwien/api/database/ViewDto.java | 36 +- .../internal/PrivilegedDatabaseDto.java | 88 --- .../database/internal/PrivilegedViewDto.java | 77 -- .../tuwien/api/database/table/TableDto.java | 36 +- .../table/internal/PrivilegedTableDto.java | 115 --- .../api/identifier/IdentifierBriefDto.java | 2 - .../main/java/at/tuwien/api/user/UserDto.java | 14 +- .../api/user/internal/PrivilegedUserDto.java | 58 -- .../java/at/tuwien/mapper/MetadataMapper.java | 72 +- .../at/tuwien/endpoints/DatabaseEndpoint.java | 76 +- .../at/tuwien/endpoints/TableEndpoint.java | 6 +- .../endpoints/DatabaseEndpointUnitTest.java | 8 +- .../gateway/DataServiceGatewayUnitTest.java | 14 +- .../gateway/SearchServiceGatewayUnitTest.java | 18 +- .../tuwien/mapper/MetadataMapperUnitTest.java | 6 - .../tuwien/service/AccessServiceUnitTest.java | 32 +- ...aCiteIdentifierServicePersistenceTest.java | 18 +- .../service/DatabaseServiceUnitTest.java | 16 +- .../IdentifierServicePersistenceTest.java | 4 +- .../service/TableServicePersistenceTest.java | 2 +- .../tuwien/service/TableServiceUnitTest.java | 20 +- .../service/ViewServicePersistenceTest.java | 2 +- .../tuwien/service/ViewServiceUnitTest.java | 4 +- .../tuwien/gateway/SearchServiceGateway.java | 4 +- .../impl/SearchServiceGatewayImpl.java | 8 +- .../java/at/tuwien/test/AbstractUnitTest.java | 50 +- .../main/java/at/tuwien/test/BaseTest.java | 134 ++-- dbrepo-search-service/Pipfile.lock | 2 +- dbrepo-search-service/init/Pipfile.lock | 17 +- .../init/lib/dbrepo-1.6.2.tar.gz | Bin 40056 -> 40094 bytes dbrepo-search-service/lib/dbrepo-1.6.2.tar.gz | Bin 40056 -> 40094 bytes .../table/[table_id]/settings.vue | 2 +- lib/python/dbrepo/api/dto.py | 46 +- 93 files changed, 2616 insertions(+), 3309 deletions(-) rename dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/{AbstractEndpoint.java => RestEndpoint.java} (98%) create mode 100644 dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/ContainerServiceIntegrationTest.java delete mode 100644 dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/SchemaServiceIntegrationTest.java create mode 100644 dbrepo-data-service/services/src/main/java/at/tuwien/service/ContainerService.java delete mode 100644 dbrepo-data-service/services/src/main/java/at/tuwien/service/SchemaService.java create mode 100644 dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/ContainerServiceMariaDbImpl.java create mode 100644 dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/DataConnector.java delete mode 100644 dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/HibernateConnector.java delete mode 100644 dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/SchemaServiceMariaDbImpl.java create mode 100644 dbrepo-metadata-service/api/src/main/java/at/tuwien/api/CacheableDto.java delete mode 100644 dbrepo-metadata-service/api/src/main/java/at/tuwien/api/PrivilegedObjectDto.java delete mode 100644 dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/internal/PrivilegedContainerDto.java delete mode 100644 dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/internal/PrivilegedDatabaseDto.java delete mode 100644 dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/internal/PrivilegedViewDto.java delete mode 100644 dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/table/internal/PrivilegedTableDto.java delete mode 100644 dbrepo-metadata-service/api/src/main/java/at/tuwien/api/user/internal/PrivilegedUserDto.java diff --git a/dbrepo-analyse-service/Pipfile.lock b/dbrepo-analyse-service/Pipfile.lock index 9f00d97ca6..ec9b5f13d4 100644 --- a/dbrepo-analyse-service/Pipfile.lock +++ b/dbrepo-analyse-service/Pipfile.lock @@ -412,7 +412,7 @@ }, "dbrepo": { "hashes": [ - "sha256:501b53c7e4b32774809f9685a18288da5b938fc1512e94d8b248f531ee8667fc" + "sha256:19c6bbcf9461e20681f0fb342087c618a91123d2d04d4df2f4fd1da80aa77b76" ], "path": "./lib/dbrepo-1.6.2.tar.gz" }, @@ -1612,7 +1612,7 @@ "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d" ], - "markers": "python_version >= '3.9'", + "markers": "python_version >= '3.10'", "version": "==2.3.0" }, "werkzeug": { @@ -2236,7 +2236,7 @@ "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d" ], - "markers": "python_version >= '3.9'", + "markers": "python_version >= '3.10'", "version": "==2.3.0" }, "wrapt": { diff --git a/dbrepo-analyse-service/lib/dbrepo-1.6.2.tar.gz b/dbrepo-analyse-service/lib/dbrepo-1.6.2.tar.gz index 58081673e955d89fccf70c9161037a725b647f71..02ed2aec31c2b1881165a12d45060ed4a311192d 100644 GIT binary patch delta 39303 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 R=>>ny-)xbb%dmiz0RTX?Dir_# delta 39288 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 LHtuT-3s@NdgDHfU diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/AccessEndpoint.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/AccessEndpoint.java index 7947e87a49..99df658030 100644 --- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/AccessEndpoint.java +++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/AccessEndpoint.java @@ -1,9 +1,9 @@ package at.tuwien.endpoints; +import at.tuwien.api.database.DatabaseDto; import at.tuwien.api.database.UpdateDatabaseAccessDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; import at.tuwien.api.error.ApiErrorDto; -import at.tuwien.api.user.internal.PrivilegedUserDto; +import at.tuwien.api.user.UserDto; import at.tuwien.exception.*; import at.tuwien.service.AccessService; import at.tuwien.service.CredentialService; @@ -29,7 +29,7 @@ import java.util.UUID; @RestController @CrossOrigin(origins = "*") @RequestMapping(path = "/api/database/{databaseId}/access") -public class AccessEndpoint extends AbstractEndpoint { +public class AccessEndpoint extends RestEndpoint { private final AccessService accessService; private final CredentialService credentialService; @@ -80,8 +80,8 @@ public class AccessEndpoint extends AbstractEndpoint { throws NotAllowedException, DatabaseUnavailableException, DatabaseNotFoundException, RemoteUnavailableException, UserNotFoundException, DatabaseMalformedException, MetadataServiceException { log.debug("endpoint give access to database, databaseId={}, userId={}", databaseId, userId); - final PrivilegedDatabaseDto database = credentialService.getDatabase(databaseId); - final PrivilegedUserDto user = credentialService.getUser(userId); + final DatabaseDto database = credentialService.getDatabase(databaseId); + final UserDto user = credentialService.getUser(userId); if (database.getAccesses().stream().anyMatch(a -> a.getUser().getId().equals(userId))) { log.error("Failed to create access to user with id {}: already has access", userId); throw new NotAllowedException("Failed to create access to user with id " + userId + ": already has access"); @@ -137,8 +137,8 @@ public class AccessEndpoint extends AbstractEndpoint { DatabaseMalformedException, MetadataServiceException { log.debug("endpoint modify access to database, databaseId={}, userId={}, access.type={}", databaseId, userId, access.getType()); - final PrivilegedDatabaseDto database = credentialService.getDatabase(databaseId); - final PrivilegedUserDto user = credentialService.getUser(userId); + final DatabaseDto database = credentialService.getDatabase(databaseId); + final UserDto user = credentialService.getUser(userId); if (database.getAccesses().stream().noneMatch(a -> a.getHuserid().equals(userId))) { log.error("Failed to update access to user with id {}: no access", userId); throw new NotAllowedException("Failed to update access to user with id " + userId + ": no access"); @@ -208,8 +208,8 @@ public class AccessEndpoint extends AbstractEndpoint { DatabaseUnavailableException, DatabaseNotFoundException, RemoteUnavailableException, UserNotFoundException, DatabaseMalformedException, MetadataServiceException { log.debug("endpoint revoke access to database, databaseId={}, userId={}", databaseId, userId); - final PrivilegedDatabaseDto database = credentialService.getDatabase(databaseId); - final PrivilegedUserDto user = credentialService.getUser(userId); + final DatabaseDto database = credentialService.getDatabase(databaseId); + final UserDto user = credentialService.getUser(userId); if (database.getAccesses().stream().noneMatch(a -> a.getUser().getId().equals(userId))) { log.error("Failed to delete access to user with id {}: no access", userId); throw new NotAllowedException("Failed to delete access to user with id " + userId + ": no access"); diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java index d101a8c973..27848cf517 100644 --- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java +++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java @@ -1,19 +1,17 @@ package at.tuwien.endpoints; -import at.tuwien.api.container.internal.PrivilegedContainerDto; +import at.tuwien.api.container.ContainerDto; import at.tuwien.api.database.AccessTypeDto; import at.tuwien.api.database.DatabaseDto; import at.tuwien.api.database.internal.CreateDatabaseDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; import at.tuwien.api.error.ApiErrorDto; -import at.tuwien.api.user.internal.PrivilegedUserDto; +import at.tuwien.api.user.UserDto; import at.tuwien.api.user.internal.UpdateUserPasswordDto; import at.tuwien.exception.*; -import at.tuwien.mapper.MetadataMapper; import at.tuwien.service.AccessService; +import at.tuwien.service.ContainerService; import at.tuwien.service.CredentialService; import at.tuwien.service.DatabaseService; -import at.tuwien.service.SubsetService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; @@ -35,21 +33,19 @@ import java.sql.SQLException; @RestController @CrossOrigin(origins = "*") @RequestMapping(path = "/api/database") -public class DatabaseEndpoint extends AbstractEndpoint { +public class DatabaseEndpoint extends RestEndpoint { - private final SubsetService queryService; private final AccessService accessService; - private final MetadataMapper metadataMapper; private final DatabaseService databaseService; + private final ContainerService containerService; private final CredentialService credentialService; @Autowired - public DatabaseEndpoint(SubsetService queryService, AccessService accessService, MetadataMapper metadataMapper, - DatabaseService databaseService, CredentialService credentialService) { - this.queryService = queryService; + public DatabaseEndpoint(AccessService accessService, DatabaseService databaseService, + ContainerService containerService, CredentialService credentialService) { this.accessService = accessService; - this.metadataMapper = metadataMapper; this.databaseService = databaseService; + this.containerService = containerService; this.credentialService = credentialService; } @@ -90,18 +86,18 @@ public class DatabaseEndpoint extends AbstractEndpoint { DatabaseMalformedException, QueryStoreCreateException, MetadataServiceException { log.debug("endpoint create database, data.containerId={}, data.internalName={}, data.username={}", data.getContainerId(), data.getInternalName(), data.getUsername()); - final PrivilegedContainerDto container = credentialService.getContainer(data.getContainerId()); + final ContainerDto container = credentialService.getContainer(data.getContainerId()); try { - final PrivilegedDatabaseDto database = databaseService.create(container, data); - queryService.createQueryStore(container, data.getInternalName()); - final PrivilegedUserDto user = PrivilegedUserDto.builder() + final DatabaseDto database = containerService.createDatabase(container, data); + containerService.createQueryStore(container, data.getInternalName()); + final UserDto user = UserDto.builder() .id(data.getUserId()) .username(data.getUsername()) .password(data.getPassword()) .build(); accessService.create(database, user, AccessTypeDto.WRITE_ALL); return ResponseEntity.status(HttpStatus.CREATED) - .body(metadataMapper.privilegedDatabaseDtoToDatabaseDto(database)); + .body(database); } catch (SQLException e) { log.error("Failed to establish connection to database: {}", e.getMessage()); throw new DatabaseUnavailableException("Failed to establish connection to database: " + e.getMessage(), e); @@ -138,7 +134,7 @@ public class DatabaseEndpoint extends AbstractEndpoint { DatabaseMalformedException, MetadataServiceException { log.debug("endpoint update user password in database, databaseId={}, data.username={}", databaseId, data.getUsername()); - final PrivilegedDatabaseDto database = credentialService.getDatabase(databaseId); + final DatabaseDto database = credentialService.getDatabase(databaseId); try { databaseService.update(database, data); return ResponseEntity.status(HttpStatus.ACCEPTED) diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/AbstractEndpoint.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/RestEndpoint.java similarity index 98% rename from dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/AbstractEndpoint.java rename to dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/RestEndpoint.java index 87a4d32532..333e0c8398 100644 --- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/AbstractEndpoint.java +++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/RestEndpoint.java @@ -11,7 +11,7 @@ import java.util.List; import java.util.Map; import java.util.UUID; -public abstract class AbstractEndpoint { +public abstract class RestEndpoint { public boolean hasRole(Principal principal, String role) { if (principal == null || role == null) { diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java index f30251a5ff..ed867715e1 100644 --- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java +++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/SubsetEndpoint.java @@ -1,9 +1,9 @@ package at.tuwien.endpoints; import at.tuwien.ExportResourceDto; +import at.tuwien.api.database.DatabaseDto; import at.tuwien.api.database.ViewColumnDto; import at.tuwien.api.database.ViewDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; import at.tuwien.api.database.query.ExecuteStatementDto; import at.tuwien.api.database.query.QueryDto; import at.tuwien.api.database.query.QueryPersistDto; @@ -46,25 +46,25 @@ import java.util.UUID; @RestController @CrossOrigin(origins = "*") @RequestMapping(path = "/api/database/{databaseId}/subset") -public class SubsetEndpoint extends AbstractEndpoint { +public class SubsetEndpoint extends RestEndpoint { - private final SchemaService schemaService; private final SubsetService subsetService; private final MetadataMapper metadataMapper; private final MetricsService metricsService; private final StorageService storageService; + private final DatabaseService databaseService; private final CredentialService credentialService; private final EndpointValidator endpointValidator; @Autowired - public SubsetEndpoint(SchemaService schemaService, SubsetService subsetService, MetadataMapper metadataMapper, - MetricsService metricsService, StorageService storageService, + public SubsetEndpoint(SubsetService subsetService, MetadataMapper metadataMapper, MetricsService metricsService, + StorageService storageService, DatabaseService databaseService, CredentialService credentialService, EndpointValidator endpointValidator) { - this.schemaService = schemaService; this.subsetService = subsetService; this.metadataMapper = metadataMapper; this.metricsService = metricsService; this.storageService = storageService; + this.databaseService = databaseService; this.credentialService = credentialService; this.endpointValidator = endpointValidator; } @@ -102,7 +102,7 @@ public class SubsetEndpoint extends AbstractEndpoint { throws DatabaseUnavailableException, DatabaseNotFoundException, RemoteUnavailableException, QueryNotFoundException, NotAllowedException, MetadataServiceException { log.debug("endpoint find subsets in database, databaseId={}, filterPersisted={}", databaseId, filterPersisted); - final PrivilegedDatabaseDto database = credentialService.getDatabase(databaseId); + final DatabaseDto database = credentialService.getDatabase(databaseId); endpointValidator.validateOnlyPrivateSchemaAccess(database, principal); final List<QueryDto> queries; try { @@ -164,7 +164,7 @@ public class SubsetEndpoint extends AbstractEndpoint { NotAllowedException { log.debug("endpoint find subset in database, databaseId={}, subsetId={}, accept={}, timestamp={}", databaseId, subsetId, accept, timestamp); - final PrivilegedDatabaseDto database = credentialService.getDatabase(databaseId); + final DatabaseDto database = credentialService.getDatabase(databaseId); endpointValidator.validateOnlyPrivateSchemaAccess(database, principal); final QueryDto subset; try { @@ -286,7 +286,7 @@ public class SubsetEndpoint extends AbstractEndpoint { log.debug("timestamp not set: default to {}", timestamp); } /* create */ - final PrivilegedDatabaseDto database = credentialService.getDatabase(databaseId); + final DatabaseDto database = credentialService.getDatabase(databaseId); endpointValidator.validateOnlyPrivateSchemaAccess(database, principal); try { final Long subsetId = subsetService.create(database, data.getStatement(), timestamp, userId); @@ -345,7 +345,7 @@ public class SubsetEndpoint extends AbstractEndpoint { log.debug("endpoint get subset data, databaseId={}, subsetId={}, principal.name={} page={}, size={}", databaseId, subsetId, principal != null ? principal.getName() : null, page, size); endpointValidator.validateDataParams(page, size); - final PrivilegedDatabaseDto database = credentialService.getDatabase(databaseId); + final DatabaseDto database = credentialService.getDatabase(databaseId); if (!database.getIsPublic()) { if (principal == null) { log.error("Failed to re-execute query: no authentication found"); @@ -377,7 +377,8 @@ public class SubsetEndpoint extends AbstractEndpoint { } final Dataset<Row> dataset = subsetService.getData(database, subset, page, size); metricsService.countSubsetGetData(databaseId, subsetId); - final ViewDto view = schemaService.inspectView(database, metadataMapper.queryDtoToViewName(subset)); + final String viewName = metadataMapper.queryDtoToViewName(subset); + final ViewDto view = databaseService.inspectView(database, viewName); headers.set("Access-Control-Expose-Headers", "X-Id X-Headers"); headers.set("X-Headers", String.join(",", view.getColumns().stream().map(ViewColumnDto::getInternalName).toList())); return ResponseEntity.status(request.getMethod().equals("POST") ? HttpStatus.CREATED : HttpStatus.OK) @@ -435,7 +436,7 @@ public class SubsetEndpoint extends AbstractEndpoint { DatabaseUnavailableException, QueryNotFoundException, UserNotFoundException, MetadataServiceException { log.debug("endpoint persist query, databaseId={}, queryId={}, data.persist={}, principal.name={}", databaseId, queryId, data.getPersist(), principal.getName()); - final PrivilegedDatabaseDto database = credentialService.getDatabase(databaseId); + final DatabaseDto database = credentialService.getDatabase(databaseId); credentialService.getAccess(databaseId, getId(principal)); try { subsetService.persist(database, queryId, data.getPersist()); diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java index f0ec00a035..077ec5b819 100644 --- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java +++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java @@ -3,11 +3,9 @@ package at.tuwien.endpoints; import at.tuwien.ExportResourceDto; import at.tuwien.api.database.DatabaseAccessDto; import at.tuwien.api.database.DatabaseDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; import at.tuwien.api.database.query.ImportDto; import at.tuwien.api.database.table.*; import at.tuwien.api.database.table.columns.ColumnDto; -import at.tuwien.api.database.table.internal.PrivilegedTableDto; import at.tuwien.api.database.table.internal.TableCreateDto; import at.tuwien.api.error.ApiErrorDto; import at.tuwien.exception.*; @@ -47,24 +45,24 @@ import java.util.Map; @RestController @CrossOrigin(origins = "*") @RequestMapping(path = "/api/database/{databaseId}/table") -public class TableEndpoint extends AbstractEndpoint { +public class TableEndpoint extends RestEndpoint { private final TableService tableService; - private final SchemaService schemaService; private final MetricsService metricsService; private final StorageService storageService; + private final DatabaseService databaseService; private final CredentialService credentialService; private final EndpointValidator endpointValidator; private final MetadataServiceGateway metadataServiceGateway; @Autowired - public TableEndpoint(TableService tableService, SchemaService schemaService, MetricsService metricsService, - StorageService storageService, CredentialService credentialService, + public TableEndpoint(TableService tableService, MetricsService metricsService, StorageService storageService, + DatabaseService databaseService, CredentialService credentialService, EndpointValidator endpointValidator, MetadataServiceGateway metadataServiceGateway) { this.tableService = tableService; - this.schemaService = schemaService; this.metricsService = metricsService; this.storageService = storageService; + this.databaseService = databaseService; this.credentialService = credentialService; this.endpointValidator = endpointValidator; this.metadataServiceGateway = metadataServiceGateway; @@ -113,11 +111,11 @@ public class TableEndpoint extends AbstractEndpoint { throw new TableMalformedException("Table must have a primary key"); } /* create */ - final PrivilegedDatabaseDto database = credentialService.getDatabase(databaseId); + final DatabaseDto database = credentialService.getDatabase(databaseId); try { - final TableDto table = tableService.createTable(database, data); + final TableDto table = databaseService.createTable(database, data); return ResponseEntity.status(HttpStatus.CREATED) - .body(schemaService.inspectTable(database, table.getInternalName())); + .body(databaseService.inspectTable(database, table.getInternalName())); } catch (SQLException e) { log.error("Failed to establish connection to database: {}", e.getMessage()); throw new DatabaseUnavailableException("Failed to establish connection to database: " + e.getMessage(), e); @@ -157,7 +155,7 @@ public class TableEndpoint extends AbstractEndpoint { TableMalformedException, DatabaseUnavailableException, TableNotFoundException, MetadataServiceException { log.debug("endpoint update table, databaseId={}, data.description={}", databaseId, data.getDescription()); /* create */ - final PrivilegedTableDto table = credentialService.getTable(databaseId, tableId); + final TableDto table = credentialService.getTable(databaseId, tableId); try { tableService.updateTable(table, data); return ResponseEntity.status(HttpStatus.ACCEPTED) @@ -200,7 +198,7 @@ public class TableEndpoint extends AbstractEndpoint { throws DatabaseUnavailableException, RemoteUnavailableException, TableNotFoundException, QueryMalformedException, MetadataServiceException { log.debug("endpoint delete table, databaseId={}, tableId={}", databaseId, tableId); - final PrivilegedTableDto table = credentialService.getTable(databaseId, tableId); + final TableDto table = credentialService.getTable(databaseId, tableId); try { tableService.delete(table); return ResponseEntity.status(HttpStatus.ACCEPTED) @@ -253,7 +251,7 @@ public class TableEndpoint extends AbstractEndpoint { @NotNull HttpServletRequest request, Principal principal) throws DatabaseUnavailableException, RemoteUnavailableException, TableNotFoundException, - PaginationException, MetadataServiceException, NotAllowedException { + PaginationException, MetadataServiceException, NotAllowedException, DatabaseNotFoundException { log.debug("endpoint get table data, databaseId={}, tableId={}, timestamp={}, page={}, size={}", databaseId, tableId, timestamp, page, size); endpointValidator.validateDataParams(page, size); @@ -270,7 +268,7 @@ public class TableEndpoint extends AbstractEndpoint { timestamp = Instant.now(); log.debug("timestamp not set: default to {}", timestamp); } - final PrivilegedTableDto table = credentialService.getTable(databaseId, tableId); + final TableDto table = credentialService.getTable(databaseId, tableId); if (!table.getIsPublic()) { if (principal == null) { log.error("Failed find table data: authentication required"); @@ -289,8 +287,8 @@ public class TableEndpoint extends AbstractEndpoint { } headers.set("Access-Control-Expose-Headers", "X-Headers"); headers.set("X-Headers", String.join(",", table.getColumns().stream().map(ColumnDto::getInternalName).toList())); - final Dataset<Row> dataset = tableService.getData(table.getDatabase(), table.getInternalName(), timestamp, - null, null, null, null); + final Dataset<Row> dataset = tableService.getData(credentialService.getDatabase(table.getTdbid()), + table.getInternalName(), timestamp, null, null, null, null); metricsService.countTableGetData(databaseId, tableId); return ResponseEntity.ok() .headers(headers) @@ -340,7 +338,7 @@ public class TableEndpoint extends AbstractEndpoint { TableMalformedException, QueryMalformedException, NotAllowedException, StorageUnavailableException, StorageNotFoundException, MetadataServiceException { log.debug("endpoint insert raw table data, databaseId={}, tableId={}", databaseId, tableId); - final PrivilegedTableDto table = credentialService.getTable(databaseId, tableId); + final TableDto table = credentialService.getTable(databaseId, tableId); final DatabaseAccessDto access = credentialService.getAccess(databaseId, getId(principal)); endpointValidator.validateOnlyWriteOwnOrWriteAllAccess(access.getType(), table.getOwner().getId(), getId(principal)); try { @@ -393,7 +391,7 @@ public class TableEndpoint extends AbstractEndpoint { TableMalformedException, QueryMalformedException, NotAllowedException, MetadataServiceException { log.debug("endpoint update raw table data, databaseId={}, tableId={}, data.keys={}", databaseId, tableId, data.getKeys()); - final PrivilegedTableDto table = credentialService.getTable(databaseId, tableId); + final TableDto table = credentialService.getTable(databaseId, tableId); final DatabaseAccessDto access = credentialService.getAccess(databaseId, getId(principal)); endpointValidator.validateOnlyWriteOwnOrWriteAllAccess(access.getType(), table.getOwner().getId(), getId(principal)); try { @@ -446,7 +444,7 @@ public class TableEndpoint extends AbstractEndpoint { TableMalformedException, QueryMalformedException, NotAllowedException, MetadataServiceException { log.debug("endpoint delete raw table data, databaseId={}, tableId={}, data.keys={}", databaseId, tableId, data.getKeys()); - final PrivilegedTableDto table = credentialService.getTable(databaseId, tableId); + final TableDto table = credentialService.getTable(databaseId, tableId); final DatabaseAccessDto access = credentialService.getAccess(databaseId, getId(principal)); endpointValidator.validateOnlyWriteOwnOrWriteAllAccess(access.getType(), table.getOwner().getId(), getId(principal)); try { @@ -506,7 +504,7 @@ public class TableEndpoint extends AbstractEndpoint { log.debug("size not set: default to 100L"); size = 100L; } - final PrivilegedTableDto table = credentialService.getTable(databaseId, tableId); + final TableDto table = credentialService.getTable(databaseId, tableId); if (!table.getIsPublic()) { if (principal == null) { log.error("Failed to find table history: no authentication found"); @@ -565,9 +563,9 @@ public class TableEndpoint extends AbstractEndpoint { throws DatabaseUnavailableException, DatabaseNotFoundException, RemoteUnavailableException, DatabaseMalformedException, TableNotFoundException, MetadataServiceException { log.debug("endpoint inspect table schemas, databaseId={}", databaseId); - final PrivilegedDatabaseDto database = credentialService.getDatabase(databaseId); + final DatabaseDto database = credentialService.getDatabase(databaseId); try { - return ResponseEntity.ok(tableService.getSchemas(database)); + return ResponseEntity.ok(databaseService.exploreTables(database)); } catch (SQLException e) { log.error("Failed to establish connection to database: {}", e.getMessage()); throw new DatabaseUnavailableException("Failed to establish connection to database: " + e.getMessage(), e); @@ -611,14 +609,14 @@ public class TableEndpoint extends AbstractEndpoint { @RequestParam(required = false) Instant timestamp, Principal principal) throws RemoteUnavailableException, TableNotFoundException, NotAllowedException, StorageUnavailableException, - QueryMalformedException, MetadataServiceException { + QueryMalformedException, MetadataServiceException, DatabaseNotFoundException { log.debug("endpoint export table data, databaseId={}, tableId={}, timestamp={}", databaseId, tableId, timestamp); /* parameters */ if (timestamp == null) { timestamp = Instant.now(); log.debug("timestamp not set: default to {}", timestamp); } - final PrivilegedTableDto table = credentialService.getTable(databaseId, tableId); + final TableDto table = credentialService.getTable(databaseId, tableId); if (!table.getIsPublic()) { if (principal == null) { log.error("Failed to export private table: principal is null"); @@ -626,8 +624,8 @@ public class TableEndpoint extends AbstractEndpoint { } credentialService.getAccess(databaseId, getId(principal)); } - final Dataset<Row> dataset = tableService.getData(table.getDatabase(), table.getInternalName(), timestamp, null, - null, null, null); + final Dataset<Row> dataset = tableService.getData(credentialService.getDatabase(table.getTdbid()), + table.getInternalName(), timestamp, null, null, null, null); metricsService.countTableGetData(databaseId, tableId); final ExportResourceDto resource = storageService.transformDataset(dataset); final HttpHeaders headers = new HttpHeaders(); @@ -677,7 +675,7 @@ public class TableEndpoint extends AbstractEndpoint { StorageNotFoundException, MalformedException, StorageUnavailableException, QueryMalformedException, DatabaseUnavailableException { log.debug("endpoint insert table data, databaseId={}, tableId={}, data.location={}", databaseId, tableId, data.getLocation()); - final PrivilegedTableDto table = credentialService.getTable(databaseId, tableId); + final TableDto table = credentialService.getTable(databaseId, tableId); final DatabaseAccessDto access = credentialService.getAccess(databaseId, getId(principal)); endpointValidator.validateOnlyWriteOwnOrWriteAllAccess(access.getType(), table.getOwner().getId(), getId(principal)); if (data.getLineTermination() == null) { @@ -725,11 +723,11 @@ public class TableEndpoint extends AbstractEndpoint { public ResponseEntity<TableStatisticDto> statistic(@NotNull @PathVariable("databaseId") Long databaseId, @NotNull @PathVariable("tableId") Long tableId) throws DatabaseUnavailableException, RemoteUnavailableException, TableNotFoundException, - MetadataServiceException, TableMalformedException { + MetadataServiceException, TableMalformedException, DatabaseNotFoundException { log.debug("endpoint generate table statistic, databaseId={}, tableId={}", databaseId, tableId); - final PrivilegedTableDto table = credentialService.getTable(databaseId, tableId); + final TableDto table = credentialService.getTable(databaseId, tableId); try { - return ResponseEntity.ok(tableService.getStatistics(table)); + return ResponseEntity.ok(tableService.getStatistics(credentialService.getDatabase(table.getTdbid()), table)); } catch (SQLException e) { log.error("Failed to establish connection to database: {}", e.getMessage()); throw new DatabaseUnavailableException("Failed to establish connection to database", e); diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/ViewEndpoint.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/ViewEndpoint.java index 2b0463483d..c662bf49c1 100644 --- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/ViewEndpoint.java +++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/ViewEndpoint.java @@ -1,11 +1,10 @@ package at.tuwien.endpoints; import at.tuwien.ExportResourceDto; +import at.tuwien.api.database.DatabaseDto; import at.tuwien.api.database.ViewColumnDto; import at.tuwien.api.database.ViewCreateDto; import at.tuwien.api.database.ViewDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; -import at.tuwien.api.database.internal.PrivilegedViewDto; import at.tuwien.api.error.ApiErrorDto; import at.tuwien.exception.*; import at.tuwien.service.*; @@ -42,23 +41,25 @@ import java.util.Map; @RestController @CrossOrigin(origins = "*") @RequestMapping(path = "/api/database/{databaseId}/view") -public class ViewEndpoint extends AbstractEndpoint { +public class ViewEndpoint extends RestEndpoint { private final ViewService viewService; private final TableService tableService; private final MetricsService metricsService; private final StorageService storageService; + private final DatabaseService databaseService; private final CredentialService credentialService; private final EndpointValidator endpointValidator; @Autowired public ViewEndpoint(ViewService viewService, TableService tableService, MetricsService metricsService, - StorageService storageService, CredentialService credentialService, - EndpointValidator endpointValidator) { + StorageService storageService, DatabaseService databaseService, + CredentialService credentialService, EndpointValidator endpointValidator) { this.viewService = viewService; this.tableService = tableService; this.metricsService = metricsService; this.storageService = storageService; + this.databaseService = databaseService; this.credentialService = credentialService; this.endpointValidator = endpointValidator; } @@ -102,11 +103,11 @@ public class ViewEndpoint extends AbstractEndpoint { }) public ResponseEntity<List<ViewDto>> getSchema(@NotNull @PathVariable("databaseId") Long databaseId) throws DatabaseUnavailableException, DatabaseNotFoundException, RemoteUnavailableException, - ViewNotFoundException, DatabaseMalformedException, MetadataServiceException { + DatabaseMalformedException, MetadataServiceException, ViewNotFoundException { log.debug("endpoint inspect view schemas, databaseId={}", databaseId); - final PrivilegedDatabaseDto database = credentialService.getDatabase(databaseId); + final DatabaseDto database = credentialService.getDatabase(databaseId); try { - return ResponseEntity.ok(viewService.getSchemas(database)); + return ResponseEntity.ok(databaseService.exploreViews(database)); } catch (SQLException e) { log.error("Failed to establish connection to database: {}", e.getMessage()); throw new DatabaseUnavailableException("Failed to establish connection to database: " + e.getMessage(), e); @@ -149,10 +150,10 @@ public class ViewEndpoint extends AbstractEndpoint { @Valid @RequestBody ViewCreateDto data) throws DatabaseUnavailableException, DatabaseNotFoundException, RemoteUnavailableException, ViewMalformedException, MetadataServiceException { log.debug("endpoint create view, databaseId={}, data.name={}", databaseId, data.getName()); - final PrivilegedDatabaseDto database = credentialService.getDatabase(databaseId); + final DatabaseDto database = credentialService.getDatabase(databaseId); try { return ResponseEntity.status(HttpStatus.CREATED) - .body(viewService.create(database, data)); + .body(databaseService.createView(database, data)); } catch (SQLException e) { log.error("Failed to establish connection to database: {}", e.getMessage()); throw new DatabaseUnavailableException("Failed to establish connection to database: " + e.getMessage(), e); @@ -193,9 +194,9 @@ public class ViewEndpoint extends AbstractEndpoint { throws DatabaseUnavailableException, RemoteUnavailableException, ViewNotFoundException, ViewMalformedException, MetadataServiceException { log.debug("endpoint delete view, databaseId={}, viewId={}", databaseId, viewId); - final PrivilegedViewDto view = credentialService.getView(databaseId, viewId); + final ViewDto view = credentialService.getView(databaseId, viewId); try { - viewService.delete(view.getDatabase(), view.getInternalName()); + viewService.delete(view); return ResponseEntity.status(HttpStatus.ACCEPTED) .build(); } catch (SQLException e) { @@ -251,7 +252,7 @@ public class ViewEndpoint extends AbstractEndpoint { @NotNull HttpServletRequest request, Principal principal) throws DatabaseUnavailableException, RemoteUnavailableException, ViewNotFoundException, PaginationException, - QueryMalformedException, NotAllowedException, MetadataServiceException, TableNotFoundException { + QueryMalformedException, NotAllowedException, MetadataServiceException, TableNotFoundException, DatabaseNotFoundException { log.debug("endpoint get view data, databaseId={}, viewId={}, page={}, size={}, timestamp={}", databaseId, viewId, page, size, timestamp); endpointValidator.validateDataParams(page, size); @@ -268,7 +269,7 @@ public class ViewEndpoint extends AbstractEndpoint { timestamp = Instant.now(); log.debug("timestamp not set: default to {}", timestamp); } - final PrivilegedViewDto view = credentialService.getView(databaseId, viewId); + final ViewDto view = credentialService.getView(databaseId, viewId); if (!view.getIsPublic()) { if (principal == null) { log.error("Failed to get data from view: unauthorized"); @@ -287,8 +288,8 @@ public class ViewEndpoint extends AbstractEndpoint { } headers.set("Access-Control-Expose-Headers", "X-Headers"); headers.set("X-Headers", String.join(",", view.getColumns().stream().map(ViewColumnDto::getInternalName).toList())); - final Dataset<Row> dataset = tableService.getData(view.getDatabase(), view.getInternalName(), timestamp, - page, size, null, null); + final Dataset<Row> dataset = tableService.getData(credentialService.getDatabase(databaseId), + view.getInternalName(), timestamp, page, size, null, null); metricsService.countViewGetData(databaseId, viewId); return ResponseEntity.ok() .headers(headers) @@ -336,7 +337,7 @@ public class ViewEndpoint extends AbstractEndpoint { @RequestParam(required = false) Instant timestamp, Principal principal) throws RemoteUnavailableException, ViewNotFoundException, NotAllowedException, MetadataServiceException, - StorageUnavailableException, QueryMalformedException, TableNotFoundException { + StorageUnavailableException, QueryMalformedException, TableNotFoundException, DatabaseNotFoundException { log.debug("endpoint export view data, databaseId={}, viewId={}", databaseId, viewId); /* parameters */ if (timestamp == null) { @@ -344,7 +345,7 @@ public class ViewEndpoint extends AbstractEndpoint { log.debug("timestamp not set: default to {}", timestamp); } /* parameters */ - final PrivilegedViewDto view = credentialService.getView(databaseId, viewId); + final ViewDto view = credentialService.getView(databaseId, viewId); if (!view.getIsPublic()) { if (principal == null) { log.error("Failed to export private view: principal is null"); @@ -352,8 +353,8 @@ public class ViewEndpoint extends AbstractEndpoint { } credentialService.getAccess(databaseId, getId(principal)); } - final Dataset<Row> dataset = tableService.getData(view.getDatabase(), view.getInternalName(), timestamp, null, - null, null, null); + final Dataset<Row> dataset = tableService.getData(credentialService.getDatabase(databaseId), + view.getInternalName(), timestamp, null, null, null, null); metricsService.countViewGetData(databaseId, viewId); final ExportResourceDto resource = storageService.transformDataset(dataset); final HttpHeaders headers = new HttpHeaders(); diff --git a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java index 25b858f51b..18307b5ac2 100644 --- a/dbrepo-data-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java +++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/validation/EndpointValidator.java @@ -2,9 +2,9 @@ package at.tuwien.validation; import at.tuwien.api.database.AccessTypeDto; import at.tuwien.api.database.DatabaseAccessDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; +import at.tuwien.api.database.DatabaseDto; import at.tuwien.config.QueryConfig; -import at.tuwien.endpoints.AbstractEndpoint; +import at.tuwien.endpoints.RestEndpoint; import at.tuwien.exception.*; import at.tuwien.gateway.MetadataServiceGateway; import lombok.extern.log4j.Log4j2; @@ -21,7 +21,7 @@ import java.util.regex.Pattern; @Log4j2 @Component -public class EndpointValidator extends AbstractEndpoint { +public class EndpointValidator extends RestEndpoint { private final QueryConfig queryConfig; private final MetadataServiceGateway metadataServiceGateway; @@ -48,12 +48,12 @@ public class EndpointValidator extends AbstractEndpoint { } } - public void validateOnlyPrivateSchemaAccess(PrivilegedDatabaseDto database, Principal principal) + public void validateOnlyPrivateSchemaAccess(DatabaseDto database, Principal principal) throws NotAllowedException, RemoteUnavailableException, MetadataServiceException { validateOnlyPrivateSchemaAccess(database, principal, false); } - public void validateOnlyPrivateSchemaAccess(PrivilegedDatabaseDto database, Principal principal, + public void validateOnlyPrivateSchemaAccess(DatabaseDto database, Principal principal, boolean writeAccessOnly) throws NotAllowedException, RemoteUnavailableException, MetadataServiceException { if (database.getIsSchemaPublic()) { @@ -63,7 +63,7 @@ public class EndpointValidator extends AbstractEndpoint { validateOnlyAccess(database, principal, writeAccessOnly); } - public void validateOnlyPrivateSchemaHasRole(PrivilegedDatabaseDto database, Principal principal, String role) + public void validateOnlyPrivateSchemaHasRole(DatabaseDto database, Principal principal, String role) throws NotAllowedException { if (database.getIsSchemaPublic()) { log.trace("database with id {} has public schema: no access needed", database.getId()); @@ -82,7 +82,7 @@ public class EndpointValidator extends AbstractEndpoint { log.trace("principal has role '{}': access granted", role); } - public void validateOnlyAccess(PrivilegedDatabaseDto database, Principal principal, boolean writeAccessOnly) + public void validateOnlyAccess(DatabaseDto database, Principal principal, boolean writeAccessOnly) throws NotAllowedException, RemoteUnavailableException, MetadataServiceException { if (principal == null) { throw new NotAllowedException("No principal provided"); diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/config/MariaDbConfig.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/config/MariaDbConfig.java index a30ffb7b81..d4daa90741 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/config/MariaDbConfig.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/config/MariaDbConfig.java @@ -1,18 +1,14 @@ package at.tuwien.config; -import at.tuwien.api.container.internal.PrivilegedContainerDto; +import at.tuwien.api.container.ContainerDto; import at.tuwien.api.database.DatabaseDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; import at.tuwien.api.database.query.QueryDto; -import at.tuwien.api.database.table.columns.ColumnTypeDto; -import at.tuwien.api.database.table.internal.PrivilegedTableDto; import lombok.extern.log4j.Log4j2; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; import java.sql.*; -import java.time.Instant; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -21,38 +17,7 @@ import java.util.regex.Pattern; @Configuration public class MariaDbConfig { - /** - * Inserts a query into a created database with given hostname and database name. The method uses the JDBC in-out - * notation <a href="#{@link}">{@link https://learn.microsoft.com/en-us/sql/connect/jdbc/using-sql-escape-sequences?view=sql-server-ver16#stored-procedure-calls}</a> - * - * @param database The database. - * @param query The query. - * @param username The connection username. - * @param password The connection password. - * @return The generated or retrieved query id. - * @throws SQLException The procedure did not succeed. - */ - public static Long mockSystemQueryInsert(PrivilegedDatabaseDto database, String query, String username, String password) - throws SQLException { - final String jdbc = "jdbc:mariadb://" + database.getContainer().getHost() + ":" + database.getContainer().getPort() + "/" + database.getInternalName(); - log.trace("connect to database {}", jdbc); - try (Connection connection = DriverManager.getConnection(jdbc, username, password)) { - final String call = "{call _store_query(?,?,?,?)}"; - log.trace("prepare procedure '{}'", call); - final CallableStatement statement = connection.prepareCall(call); - statement.setString(1, username); - statement.setString(2, query); - statement.setTimestamp(3, Timestamp.from(Instant.now())); - statement.registerOutParameter(4, Types.BIGINT); - statement.executeUpdate(); - final Long queryId = statement.getLong(4); - statement.close(); - log.debug("received queryId={}", queryId); - return queryId; - } - } - - public static void createDatabase(PrivilegedContainerDto container, String database) throws SQLException { + public static void createDatabase(ContainerDto container, String database) throws SQLException { final String jdbc = "jdbc:mariadb://" + container.getHost() + ":" + container.getPort(); log.trace("connect to database {}", jdbc); try (Connection connection = DriverManager.getConnection(jdbc, container.getUsername(), container.getPassword())) { @@ -65,7 +30,7 @@ public class MariaDbConfig { log.debug("created database {}", database); } - public static void createInitDatabase(PrivilegedContainerDto container, DatabaseDto database) throws SQLException { + public static void createInitDatabase(ContainerDto container, DatabaseDto database) throws SQLException { final String jdbc = "jdbc:mariadb://" + container.getHost() + ":" + container.getPort(); log.trace("connect to database {}", jdbc); try (Connection connection = DriverManager.getConnection(jdbc, container.getUsername(), container.getPassword())) { @@ -76,21 +41,7 @@ public class MariaDbConfig { log.debug("created init database {}", database.getInternalName()); } - public static void grantReadAccess(PrivilegedDatabaseDto database, String username) { - final String jdbc = "jdbc:mariadb://" + database.getContainer().getHost() + ":" + database.getContainer().getPort() + "/" + database.getInternalName(); - log.trace("connect to database {}", jdbc); - try (Connection connection = DriverManager.getConnection(jdbc, database.getContainer().getUsername(), database.getContainer().getPassword())) { - connection.prepareStatement("GRANT SELECT ON *.* TO `" + username + "`@`%`;") - .executeUpdate(); - connection.prepareStatement("FLUSH PRIVILEGES;") - .executeUpdate(); - } catch (SQLException e) { - log.error("could not grant read access", e); - } - log.debug("granted read access to user {} in database {}", username, database.getInternalName()); - } - - public static void grantWriteAccess(PrivilegedDatabaseDto database, String username) { + public static void grantWriteAccess(DatabaseDto database, String username) { final String jdbc = "jdbc:mariadb://" + database.getContainer().getHost() + ":" + database.getContainer().getPort() + "/" + database.getInternalName(); log.trace("connect to database {}", jdbc); try (Connection connection = DriverManager.getConnection(jdbc, database.getContainer().getUsername(), database.getContainer().getPassword())) { @@ -104,7 +55,7 @@ public class MariaDbConfig { log.debug("granted read access to user {} in database {}", username, database.getInternalName()); } - public static void dropAllDatabases(PrivilegedContainerDto container) { + public static void dropAllDatabases(ContainerDto container) { final String jdbc = "jdbc:mariadb://" + container.getHost() + ":" + container.getPort(); log.trace("connect to database {}", jdbc); try (Connection connection = DriverManager.getConnection(jdbc, container.getUsername(), container.getPassword())) { @@ -130,7 +81,7 @@ public class MariaDbConfig { log.debug("dropped all databases"); } - public static void dropDatabase(PrivilegedContainerDto container, String database) + public static void dropDatabase(ContainerDto container, String database) throws SQLException { final String jdbc = "jdbc:mariadb://" + container.getHost() + ":" + container.getPort(); log.trace("connect to database {}", jdbc); @@ -144,26 +95,7 @@ public class MariaDbConfig { log.debug("dropped database {}", database); } - public static List<String> getUsernames(String hostname, String database, String username, String password) - throws SQLException { - final String jdbc = "jdbc:mariadb://" + hostname + "/" + database; - log.trace("connect to database {}", jdbc); - final List<String> usernames = new LinkedList<>(); - try (Connection connection = DriverManager.getConnection(jdbc, username, password)) { - final String query = "SELECT User FROM mysql.user;"; - log.trace("prepare statement '{}'", query); - final PreparedStatement statement = connection.prepareStatement(query); - final ResultSet set = statement.executeQuery(); - statement.close(); - while (set.next()) { - usernames.add(set.getString("User")); - } - log.debug("received usernames={}", usernames); - return usernames; - } - } - - public static List<String> getPrivileges(PrivilegedDatabaseDto database, String username) throws SQLException { + public static List<String> getPrivileges(DatabaseDto database, String username) throws SQLException { final String jdbc = "jdbc:mariadb://" + database.getContainer().getHost() + ":" + database.getContainer().getPort() + "/" + database.getInternalName(); log.trace("connect to database {}", jdbc); try (Connection connection = DriverManager.getConnection(jdbc, database.getContainer().getUsername(), database.getContainer().getPassword())) { @@ -185,7 +117,7 @@ public class MariaDbConfig { throw new SQLException("Failed to get privileges"); } - public static void dropTable(PrivilegedDatabaseDto database, String table) throws SQLException { + public static void dropTable(DatabaseDto database, String table) throws SQLException { final String jdbc = "jdbc:mariadb://" + database.getContainer().getHost() + ":" + database.getContainer().getPort() + "/" + database.getInternalName(); log.trace("connect to database {}", jdbc); try (Connection connection = DriverManager.getConnection(jdbc, database.getContainer().getUsername(), database.getContainer().getPassword())) { @@ -209,50 +141,7 @@ public class MariaDbConfig { } } - /** - * Inserts a query into a created database with given hostname and database name. The method uses the JDBC in-out - * notation <a href="#{@link}">{@link https://learn.microsoft.com/en-us/sql/connect/jdbc/using-sql-escape-sequences?view=sql-server-ver16#stored-procedure-calls}</a> - * - * @param database The database. - * @param query The query. - * @param username The connection username. - * @param password The connection password. - * @return The generated or retrieved query id. - * @throws SQLException The procedure did not succeed. - */ - public static Long mockUserQueryInsert(PrivilegedDatabaseDto database, String query, String username, String password) - throws SQLException { - final String jdbc = "jdbc:mariadb://" + database.getContainer().getHost() + ":" + database.getContainer().getPort() + "/" + database.getInternalName(); - log.trace("connect to database: {}", jdbc); - try (Connection connection = DriverManager.getConnection(jdbc, username, password)) { - final String call = "{call store_query(?,?,?)}"; - log.trace("prepare procedure '{}'", call); - final CallableStatement statement = connection.prepareCall(call); - statement.setString(1, query); - statement.setTimestamp(2, Timestamp.from(Instant.now())); - statement.registerOutParameter(3, Types.BIGINT); - statement.executeUpdate(); - final Long queryId = statement.getLong(3); - statement.close(); - log.debug("received queryId={}", queryId); - return queryId; - } - } - - /** - * Inserts a query into a created database with given hostname and database name. The method uses the JDBC in-out - * notation <a href="#{@link}">{@link https://learn.microsoft.com/en-us/sql/connect/jdbc/using-sql-escape-sequences?view=sql-server-ver16#stored-procedure-calls}</a> - * - * @param database The database. - * @param query The query. - * @return The generated or retrieved query id. - * @throws SQLException The procedure did not succeed. - */ - public static Long mockSystemQueryInsert(PrivilegedDatabaseDto database, String query) throws SQLException { - return mockSystemQueryInsert(database, query, database.getContainer().getUsername(), database.getContainer().getPassword()); - } - - public static void insertQueryStore(PrivilegedDatabaseDto database, QueryDto query, UUID userId) throws SQLException { + public static void insertQueryStore(DatabaseDto database, QueryDto query, UUID userId) throws SQLException { final String jdbc = "jdbc:mariadb://" + database.getContainer().getHost() + ":" + database.getContainer().getPort() + "/" + database.getInternalName(); log.trace("connect to database: {}", jdbc); try (Connection connection = DriverManager.getConnection(jdbc, database.getContainer().getUsername(), database.getContainer().getPassword())) { @@ -272,7 +161,7 @@ public class MariaDbConfig { } } - public static List<Map<String, Object>> listQueryStore(PrivilegedDatabaseDto database) throws SQLException { + public static List<Map<String, Object>> listQueryStore(DatabaseDto database) throws SQLException { final String jdbc = "jdbc:mariadb://" + database.getContainer().getHost() + ":" + database.getContainer().getPort() + "/" + database.getInternalName(); log.trace("connect to database {}", jdbc); try (Connection connection = DriverManager.getConnection(jdbc, database.getContainer().getUsername(), database.getContainer().getPassword())) { @@ -297,7 +186,7 @@ public class MariaDbConfig { } } - public static List<Map<String, String>> selectQuery(PrivilegedDatabaseDto database, String query, Set<String> columns) + public static List<Map<String, String>> selectQuery(DatabaseDto database, String query, Set<String> columns) throws SQLException { final String jdbc = "jdbc:mariadb://" + database.getContainer().getHost() + ":" + database.getContainer().getPort() + "/" + database.getInternalName(); log.trace("connect to database {}", jdbc); @@ -318,7 +207,7 @@ public class MariaDbConfig { return rows; } - public static List<Map<String, byte[]>> selectQueryByteArr(PrivilegedDatabaseDto database, String query, Set<String> columns) + public static List<Map<String, byte[]>> selectQueryByteArr(DatabaseDto database, String query, Set<String> columns) throws SQLException { final String jdbc = "jdbc:mariadb://" + database.getContainer().getHost() + ":" + database.getContainer().getPort() + "/" + database.getInternalName(); log.trace("connect to database {}", jdbc); @@ -339,7 +228,7 @@ public class MariaDbConfig { return rows; } - public static void execute(PrivilegedDatabaseDto database, String query) + public static void execute(DatabaseDto database, String query) throws SQLException { final String jdbc = "jdbc:mariadb://" + database.getContainer().getHost() + ":" + database.getContainer().getPort() + "/" + database.getInternalName(); log.trace("connect to database {}", jdbc); @@ -349,17 +238,7 @@ public class MariaDbConfig { } } - public static void execute(PrivilegedContainerDto container, String query) - throws SQLException { - final String jdbc = "jdbc:mariadb://" + container.getHost() + ":" + container.getPort(); - log.trace("connect to database: {}", jdbc); - try (Connection connection = DriverManager.getConnection(jdbc, container.getUsername(), container.getPassword())) { - final Statement statement = connection.createStatement(); - statement.executeUpdate(query); - } - } - - public static void dropQueryStore(PrivilegedDatabaseDto database) + public static void dropQueryStore(DatabaseDto database) throws SQLException { final String jdbc = "jdbc:mariadb://" + database.getContainer().getHost() + ":" + database.getContainer().getPort() + "/" + database.getInternalName(); log.trace("connect to database: {}", jdbc); @@ -373,78 +252,4 @@ public class MariaDbConfig { } } - public static Map<String, List<Object>> describeTableSchema(PrivilegedTableDto table, String username, String password) - throws SQLException { - final String jdbc = "jdbc:mariadb://" + table.getDatabase().getContainer().getHost() + ":" + table.getDatabase().getContainer().getPort() + "/" + table.getDatabase().getInternalName(); - log.trace("connect to database {}", jdbc); - final Map<String, List<Object>> out = new HashMap<>(); - try (Connection connection = DriverManager.getConnection(jdbc, username, password)) { - final String query = "SHOW COLUMNS FROM `" + table.getInternalName() + "`;"; - log.trace("prepare statement '{}'", query); - final PreparedStatement statement = connection.prepareStatement(query); - final ResultSet resultSet = statement.executeQuery(); - statement.close(); - while (resultSet.next()) { - if (resultSet.getString("Field").equals("id")) { - continue; - } - out.put(resultSet.getString("Field"), List.of(resultSet.getString("Type"), resultSet.getString("Null"), resultSet.getString("Key"))); - } - return out; - } - } - - public static ColumnTypeDto typetoColumnTypeDto(String data) throws Exception { - if (data.toUpperCase().startsWith("TINYINT(1)")) { - /* boolean in MySQL */ - return ColumnTypeDto.BOOL; - } - final Matcher matcher = Pattern.compile("([A-Z]+)") - .matcher(data.toUpperCase()); - if (!matcher.find()) { - log.error("Failed to map type: does not match expected format"); - throw new Exception("Failed to map type: does not match expected format"); - } - final String type = matcher.group(1); - try { - return ColumnTypeDto.valueOf(type); - } catch (IllegalArgumentException e) { - if (type.startsWith("TINYINT")) { - /* boolean in MySQL */ - return ColumnTypeDto.BOOL; - } else if (type.startsWith("BOOL")) { - /* boolean */ - return ColumnTypeDto.BOOL; - } else if (type.startsWith("DOUBLE")) { - /* double precision */ - return ColumnTypeDto.DOUBLE; - } else if (type.startsWith("INT")) { - /* integer synonym */ - return ColumnTypeDto.INT; - } else if (type.startsWith("DEC")) { - /* decimal synonym */ - return ColumnTypeDto.DECIMAL; - } else if (type.startsWith("ENUM")) { - return ColumnTypeDto.ENUM; - } else if (type.startsWith("SET")) { - return ColumnTypeDto.SET; - } - } - log.error("Failed to map data {} and type {}", data, type); - throw new Exception("Failed to map data " + data + " and type " + type); - } - - public static boolean tableExists(PrivilegedDatabaseDto database, String tableName) - throws SQLException { - final String jdbc = "jdbc:mariadb://" + database.getContainer().getHost() + ":" + database.getContainer().getPort() + "/" + database.getInternalName(); - log.trace("connect to database {}", jdbc); - try (Connection connection = DriverManager.getConnection(jdbc, database.getContainer().getUsername(), database.getContainer().getPassword())) { - final Statement statement = connection.createStatement(); - final String query = "SHOW TABLES LIKE '" + tableName + "';"; - log.trace("execute query {}", query); - final ResultSet result = statement.executeQuery(query); - return result.next(); - } - } - } diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/AccessEndpointUnitTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/AccessEndpointUnitTest.java index 9fb4003dba..00553dce06 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/AccessEndpointUnitTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/AccessEndpointUnitTest.java @@ -1,8 +1,8 @@ package at.tuwien.endpoint; import at.tuwien.api.database.AccessTypeDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; -import at.tuwien.api.user.internal.PrivilegedUserDto; +import at.tuwien.api.database.DatabaseDto; +import at.tuwien.api.user.UserDto; import at.tuwien.endpoints.AccessEndpoint; import at.tuwien.exception.*; import at.tuwien.service.AccessService; @@ -51,7 +51,7 @@ public class AccessEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); + .thenReturn(DATABASE_1_DTO); when(credentialService.getUser(USER_4_ID)) .thenReturn(USER_4_PRIVILEGED_DTO); @@ -68,7 +68,7 @@ public class AccessEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); + .thenReturn(DATABASE_1_DTO); when(credentialService.getUser(USER_1_ID)) .thenReturn(USER_1_PRIVILEGED_DTO); @@ -85,12 +85,12 @@ public class AccessEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); + .thenReturn(DATABASE_1_DTO); when(credentialService.getUser(USER_4_ID)) .thenReturn(USER_4_PRIVILEGED_DTO); doThrow(SQLException.class) .when(accessService) - .create(DATABASE_1_PRIVILEGED_DTO, USER_4_PRIVILEGED_DTO, AccessTypeDto.READ); + .create(DATABASE_1_DTO, USER_4_PRIVILEGED_DTO, AccessTypeDto.READ); /* test */ assertThrows(DatabaseUnavailableException.class, () -> { @@ -121,7 +121,7 @@ public class AccessEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); + .thenReturn(DATABASE_1_DTO); doThrow(UserNotFoundException.class) .when(credentialService) .getUser(USER_4_ID); @@ -149,7 +149,7 @@ public class AccessEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); + .thenReturn(DATABASE_1_DTO); when(credentialService.getUser(USER_1_ID)) .thenReturn(USER_1_PRIVILEGED_DTO); @@ -166,12 +166,12 @@ public class AccessEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); + .thenReturn(DATABASE_1_DTO); when(credentialService.getUser(USER_1_ID)) .thenReturn(USER_1_PRIVILEGED_DTO); doThrow(SQLException.class) .when(accessService) - .update(DATABASE_1_PRIVILEGED_DTO, USER_1_PRIVILEGED_DTO, AccessTypeDto.READ); + .update(DATABASE_1_DTO, USER_1_PRIVILEGED_DTO, AccessTypeDto.READ); /* test */ assertThrows(DatabaseUnavailableException.class, () -> { @@ -186,7 +186,7 @@ public class AccessEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); + .thenReturn(DATABASE_1_DTO); when(credentialService.getUser(USER_4_ID)) .thenReturn(USER_4_PRIVILEGED_DTO); @@ -229,7 +229,7 @@ public class AccessEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); + .thenReturn(DATABASE_1_DTO); doThrow(UserNotFoundException.class) .when(credentialService) .getUser(USER_1_ID); @@ -248,12 +248,12 @@ public class AccessEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); + .thenReturn(DATABASE_1_DTO); when(credentialService.getUser(USER_1_ID)) .thenReturn(USER_1_PRIVILEGED_DTO); doNothing() .when(accessService) - .delete(any(PrivilegedDatabaseDto.class), any(PrivilegedUserDto.class)); + .delete(any(DatabaseDto.class), any(UserDto.class)); /* test */ final ResponseEntity<Void> response = accessEndpoint.revoke(DATABASE_1_ID, USER_1_ID); @@ -268,7 +268,7 @@ public class AccessEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); + .thenReturn(DATABASE_1_DTO); when(credentialService.getUser(USER_4_ID)) .thenReturn(USER_4_PRIVILEGED_DTO); @@ -311,7 +311,7 @@ public class AccessEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); + .thenReturn(DATABASE_1_DTO); doThrow(UserNotFoundException.class) .when(credentialService) .getUser(USER_1_ID); @@ -329,12 +329,12 @@ public class AccessEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); + .thenReturn(DATABASE_1_DTO); when(credentialService.getUser(USER_1_ID)) .thenReturn(USER_1_PRIVILEGED_DTO); doThrow(SQLException.class) .when(accessService) - .delete(DATABASE_1_PRIVILEGED_DTO, USER_1_PRIVILEGED_DTO); + .delete(DATABASE_1_DTO, USER_1_PRIVILEGED_DTO); /* test */ assertThrows(DatabaseUnavailableException.class, () -> { diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/DatabaseEndpointUnitTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/DatabaseEndpointUnitTest.java index c55442290e..592063e034 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/DatabaseEndpointUnitTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/DatabaseEndpointUnitTest.java @@ -2,13 +2,10 @@ package at.tuwien.endpoint; import at.tuwien.api.database.AccessTypeDto; import at.tuwien.api.database.DatabaseDto; -import at.tuwien.api.user.internal.PrivilegedUserDto; +import at.tuwien.api.user.UserDto; import at.tuwien.endpoints.DatabaseEndpoint; import at.tuwien.exception.*; -import at.tuwien.service.AccessService; -import at.tuwien.service.CredentialService; -import at.tuwien.service.DatabaseService; -import at.tuwien.service.SubsetService; +import at.tuwien.service.*; import at.tuwien.test.AbstractUnitTest; import lombok.extern.log4j.Log4j2; import org.junit.jupiter.api.BeforeEach; @@ -41,6 +38,9 @@ public class DatabaseEndpointUnitTest extends AbstractUnitTest { @MockBean private SubsetService queryService; + @MockBean + private ContainerService containerService; + @MockBean private AccessService accessService; @@ -63,15 +63,15 @@ public class DatabaseEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getContainer(CONTAINER_1_ID)) - .thenReturn(CONTAINER_1_PRIVILEGED_DTO); - when(databaseService.create(CONTAINER_1_PRIVILEGED_DTO, DATABASE_1_CREATE_INTERNAL)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); + .thenReturn(CONTAINER_1_DTO); + when(containerService.createDatabase(CONTAINER_1_DTO, DATABASE_1_CREATE_INTERNAL)) + .thenReturn(DATABASE_1_DTO); doNothing() - .when(queryService) - .createQueryStore(CONTAINER_1_PRIVILEGED_DTO, DATABASE_1_INTERNALNAME); + .when(containerService) + .createQueryStore(CONTAINER_1_DTO, DATABASE_1_INTERNALNAME); doNothing() .when(accessService) - .create(eq(DATABASE_1_PRIVILEGED_DTO), any(PrivilegedUserDto.class), any(AccessTypeDto.class)); + .create(eq(DATABASE_1_DTO), any(UserDto.class), any(AccessTypeDto.class)); /* test */ final ResponseEntity<DatabaseDto> response = databaseEndpoint.create(DATABASE_1_CREATE_INTERNAL); @@ -85,15 +85,15 @@ public class DatabaseEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getContainer(CONTAINER_1_ID)) - .thenReturn(CONTAINER_1_PRIVILEGED_DTO); - when(databaseService.create(CONTAINER_1_PRIVILEGED_DTO, DATABASE_1_CREATE_INTERNAL)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); + .thenReturn(CONTAINER_1_DTO); + when(containerService.createDatabase(CONTAINER_1_DTO, DATABASE_1_CREATE_INTERNAL)) + .thenReturn(DATABASE_1_DTO); doNothing() - .when(queryService) - .createQueryStore(CONTAINER_1_PRIVILEGED_DTO, DATABASE_1_INTERNALNAME); + .when(containerService) + .createQueryStore(CONTAINER_1_DTO, DATABASE_1_INTERNALNAME); doNothing() .when(accessService) - .create(eq(DATABASE_1_PRIVILEGED_DTO), any(PrivilegedUserDto.class), any(AccessTypeDto.class)); + .create(eq(DATABASE_1_DTO), any(UserDto.class), any(AccessTypeDto.class)); /* test */ assertThrows(org.springframework.security.access.AccessDeniedException.class, () -> { @@ -108,10 +108,10 @@ public class DatabaseEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getContainer(CONTAINER_1_ID)) - .thenReturn(CONTAINER_1_PRIVILEGED_DTO); + .thenReturn(CONTAINER_1_DTO); doThrow(SQLException.class) - .when(databaseService) - .create(CONTAINER_1_PRIVILEGED_DTO, DATABASE_1_CREATE_INTERNAL); + .when(containerService) + .createDatabase(CONTAINER_1_DTO, DATABASE_1_CREATE_INTERNAL); /* test */ assertThrows(DatabaseUnavailableException.class, () -> { @@ -144,11 +144,11 @@ public class DatabaseEndpointUnitTest extends AbstractUnitTest { doThrow(ContainerNotFoundException.class) .when(credentialService) .getContainer(CONTAINER_1_ID); - when(databaseService.create(CONTAINER_1_PRIVILEGED_DTO, DATABASE_1_CREATE_INTERNAL)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); + when(containerService.createDatabase(CONTAINER_1_DTO, DATABASE_1_CREATE_INTERNAL)) + .thenReturn(DATABASE_1_DTO); doThrow(QueryStoreCreateException.class) - .when(queryService) - .createQueryStore(CONTAINER_1_PRIVILEGED_DTO, DATABASE_1_INTERNALNAME); + .when(containerService) + .createQueryStore(CONTAINER_1_DTO, DATABASE_1_INTERNALNAME); /* test */ assertThrows(ContainerNotFoundException.class, () -> { @@ -163,7 +163,7 @@ public class DatabaseEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); + .thenReturn(DATABASE_1_DTO); /* test */ databaseEndpoint.update(DATABASE_1_ID, USER_1_UPDATE_PASSWORD_DTO); @@ -176,10 +176,10 @@ public class DatabaseEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); + .thenReturn(DATABASE_1_DTO); doThrow(SQLException.class) .when(databaseService) - .update(DATABASE_1_PRIVILEGED_DTO, USER_1_UPDATE_PASSWORD_DTO); + .update(DATABASE_1_DTO, USER_1_UPDATE_PASSWORD_DTO); /* test */ assertThrows(DatabaseUnavailableException.class, () -> { @@ -193,7 +193,7 @@ public class DatabaseEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); + .thenReturn(DATABASE_1_DTO); /* test */ assertThrows(org.springframework.security.access.AccessDeniedException.class, () -> { @@ -224,10 +224,10 @@ public class DatabaseEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); + .thenReturn(DATABASE_1_DTO); doThrow(DatabaseMalformedException.class) .when(databaseService) - .update(DATABASE_1_PRIVILEGED_DTO, USER_1_UPDATE_PASSWORD_DTO); + .update(DATABASE_1_DTO, USER_1_UPDATE_PASSWORD_DTO); /* test */ assertThrows(DatabaseMalformedException.class, () -> { diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/SubsetEndpointUnitTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/SubsetEndpointUnitTest.java index 8a08f8231f..c76aa91ebf 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/SubsetEndpointUnitTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/SubsetEndpointUnitTest.java @@ -1,6 +1,6 @@ package at.tuwien.endpoint; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; +import at.tuwien.api.database.DatabaseDto; import at.tuwien.api.database.query.ExecuteStatementDto; import at.tuwien.api.database.query.QueryDto; import at.tuwien.api.database.query.QueryPersistDto; @@ -8,7 +8,6 @@ import at.tuwien.endpoints.SubsetEndpoint; import at.tuwien.exception.*; import at.tuwien.gateway.MetadataServiceGateway; import at.tuwien.service.CredentialService; -import at.tuwien.service.SchemaService; import at.tuwien.service.StorageService; import at.tuwien.service.SubsetService; import at.tuwien.test.AbstractUnitTest; @@ -25,7 +24,6 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.security.test.context.support.WithAnonymousUser; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -53,9 +51,6 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { @MockBean private SubsetService subsetService; - @MockBean - private SchemaService schemaService; - @MockBean private MetadataServiceGateway metadataServiceGateway; @@ -68,9 +63,6 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { @MockBean private CredentialService credentialService; - @MockBean - private MockHttpServletRequest mockHttpServletRequest; - @BeforeEach public void beforeEach() { genesis(); @@ -82,12 +74,12 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { RemoteUnavailableException, SQLException, MetadataServiceException { /* mock */ - when(subsetService.findAll(DATABASE_3_PRIVILEGED_DTO, null)) + when(subsetService.findAll(DATABASE_3_DTO, null)) .thenReturn(List.of(QUERY_1_DTO, QUERY_2_DTO, QUERY_3_DTO, QUERY_4_DTO, QUERY_5_DTO, QUERY_6_DTO)); /* test */ assertThrows(NotAllowedException.class, () -> { - generic_list(DATABASE_3_ID, DATABASE_3_PRIVILEGED_DTO, null); + generic_list(DATABASE_3_ID, DATABASE_3_DTO, null); }); } @@ -98,11 +90,11 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { MetadataServiceException { /* mock */ - when(subsetService.findAll(DATABASE_3_PRIVILEGED_DTO, null)) + when(subsetService.findAll(DATABASE_3_DTO, null)) .thenReturn(List.of(QUERY_1_DTO, QUERY_2_DTO, QUERY_3_DTO, QUERY_4_DTO, QUERY_5_DTO, QUERY_6_DTO)); /* test */ - final List<QueryDto> response = generic_list(DATABASE_3_ID, DATABASE_3_PRIVILEGED_DTO, USER_3_PRINCIPAL); + final List<QueryDto> response = generic_list(DATABASE_3_ID, DATABASE_3_DTO, USER_3_PRINCIPAL); assertEquals(6, response.size()); } @@ -123,14 +115,14 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_3_ID)) - .thenReturn(DATABASE_3_PRIVILEGED_DTO); + .thenReturn(DATABASE_3_DTO); doThrow(SQLException.class) .when(subsetService) - .findAll(DATABASE_3_PRIVILEGED_DTO, null); + .findAll(DATABASE_3_DTO, null); /* test */ assertThrows(DatabaseUnavailableException.class, () -> { - generic_list(DATABASE_3_ID, DATABASE_3_PRIVILEGED_DTO, USER_3_PRINCIPAL); + generic_list(DATABASE_3_ID, DATABASE_3_DTO, USER_3_PRINCIPAL); }); } @@ -141,8 +133,8 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); - when(subsetService.findById(DATABASE_1_PRIVILEGED_DTO, QUERY_1_ID)) + .thenReturn(DATABASE_1_DTO); + when(subsetService.findById(DATABASE_1_DTO, QUERY_1_ID)) .thenReturn(QUERY_1_DTO); /* test */ @@ -158,8 +150,8 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); - when(subsetService.findById(DATABASE_1_PRIVILEGED_DTO, QUERY_1_ID)) + .thenReturn(DATABASE_1_DTO); + when(subsetService.findById(DATABASE_1_DTO, QUERY_1_ID)) .thenReturn(QUERY_1_DTO); /* test */ @@ -176,8 +168,8 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_3_ID)) - .thenReturn(DATABASE_3_PRIVILEGED_DTO); - when(subsetService.findById(DATABASE_3_PRIVILEGED_DTO, QUERY_5_ID)) + .thenReturn(DATABASE_3_DTO); + when(subsetService.findById(DATABASE_3_DTO, QUERY_5_ID)) .thenReturn(QUERY_5_DTO); /* test */ @@ -191,8 +183,8 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_4_ID)) - .thenReturn(DATABASE_4_PRIVILEGED_DTO); - when(subsetService.findById(DATABASE_4_PRIVILEGED_DTO, QUERY_7_ID)) + .thenReturn(DATABASE_4_DTO); + when(subsetService.findById(DATABASE_4_DTO, QUERY_7_ID)) .thenReturn(QUERY_7_DTO); /* test */ @@ -211,12 +203,12 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); - when(subsetService.findById(DATABASE_1_PRIVILEGED_DTO, QUERY_1_ID)) + .thenReturn(DATABASE_1_DTO); + when(subsetService.findById(DATABASE_1_DTO, QUERY_1_ID)) .thenReturn(QUERY_5_DTO); when(storageService.transformDataset(any(Dataset.class))) .thenReturn(EXPORT_RESOURCE_DTO); - when(subsetService.getData(any(PrivilegedDatabaseDto.class), any(QueryDto.class), eq(null), eq(null))) + when(subsetService.getData(any(DatabaseDto.class), any(QueryDto.class), eq(null), eq(null))) .thenReturn(mock); /* test */ @@ -233,10 +225,10 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_3_ID)) - .thenReturn(DATABASE_3_PRIVILEGED_DTO); - when(subsetService.findById(DATABASE_3_PRIVILEGED_DTO, QUERY_5_ID)) + .thenReturn(DATABASE_3_DTO); + when(subsetService.findById(DATABASE_3_DTO, QUERY_5_ID)) .thenReturn(QUERY_5_DTO); - when(subsetService.getData(any(PrivilegedDatabaseDto.class), any(QueryDto.class), eq(null), eq(null))) + when(subsetService.getData(any(DatabaseDto.class), any(QueryDto.class), eq(null), eq(null))) .thenReturn(mock); when(storageService.transformDataset(any(Dataset.class))) .thenReturn(EXPORT_RESOURCE_DTO); @@ -256,10 +248,10 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_3_ID)) - .thenReturn(DATABASE_3_PRIVILEGED_DTO); - when(subsetService.findById(DATABASE_4_PRIVILEGED_DTO, QUERY_5_ID)) + .thenReturn(DATABASE_3_DTO); + when(subsetService.findById(DATABASE_4_DTO, QUERY_5_ID)) .thenReturn(QUERY_5_DTO); - when(subsetService.getData(any(PrivilegedDatabaseDto.class), any(QueryDto.class), eq(null), eq(null))) + when(subsetService.getData(any(DatabaseDto.class), any(QueryDto.class), eq(null), eq(null))) .thenReturn(mock); when(storageService.transformDataset(any(Dataset.class))) .thenReturn(EXPORT_RESOURCE_DTO); @@ -293,10 +285,10 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_3_ID)) - .thenReturn(DATABASE_3_PRIVILEGED_DTO); + .thenReturn(DATABASE_3_DTO); doThrow(SQLException.class) .when(subsetService) - .findById(DATABASE_3_PRIVILEGED_DTO, QUERY_5_ID); + .findById(DATABASE_3_DTO, QUERY_5_ID); /* test */ assertThrows(DatabaseUnavailableException.class, () -> { @@ -312,14 +304,14 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_3_ID)) - .thenReturn(DATABASE_3_PRIVILEGED_DTO); - when(subsetService.findById(DATABASE_3_PRIVILEGED_DTO, QUERY_5_ID)) + .thenReturn(DATABASE_3_DTO); + when(subsetService.findById(DATABASE_3_DTO, QUERY_5_ID)) .thenReturn(QUERY_5_DTO); when(storageService.transformDataset(any(Dataset.class))) .thenReturn(EXPORT_RESOURCE_DTO); doThrow(SQLException.class) .when(subsetService) - .getData(eq(DATABASE_3_PRIVILEGED_DTO), eq(QUERY_5_DTO), eq(null), eq(null)); + .getData(eq(DATABASE_3_DTO), eq(QUERY_5_DTO), eq(null), eq(null)); /* test */ assertThrows(DatabaseUnavailableException.class, () -> { @@ -341,13 +333,11 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_3_ID)) - .thenReturn(DATABASE_3_PRIVILEGED_DTO); - when(subsetService.getData(eq(DATABASE_3_PRIVILEGED_DTO), any(QueryDto.class), eq(0L), eq(10L))) + .thenReturn(DATABASE_3_DTO); + when(subsetService.getData(eq(DATABASE_3_DTO), any(QueryDto.class), eq(0L), eq(10L))) .thenReturn(mock); - when(subsetService.findById(eq(DATABASE_3_PRIVILEGED_DTO), anyLong())) + when(subsetService.findById(eq(DATABASE_3_DTO), anyLong())) .thenReturn(QUERY_5_DTO); - when(schemaService.inspectView(eq(DATABASE_3_PRIVILEGED_DTO), anyString())) - .thenReturn(VIEW_5_DTO); when(httpServletRequest.getMethod()) .thenReturn("POST"); @@ -387,12 +377,10 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_3_ID)) - .thenReturn(DATABASE_3_PRIVILEGED_DTO); - when(schemaService.inspectView(eq(DATABASE_3_PRIVILEGED_DTO), anyString())) - .thenReturn(VIEW_5_DTO); - when(subsetService.findById(eq(DATABASE_3_PRIVILEGED_DTO), anyLong())) + .thenReturn(DATABASE_3_DTO); + when(subsetService.findById(eq(DATABASE_3_DTO), anyLong())) .thenReturn(QUERY_5_DTO); - when(subsetService.getData(eq(DATABASE_3_PRIVILEGED_DTO), any(QueryDto.class), eq(0L), eq(10L))) + when(subsetService.getData(eq(DATABASE_3_DTO), any(QueryDto.class), eq(0L), eq(10L))) .thenReturn(mock); when(httpServletRequest.getMethod()) .thenReturn("POST"); @@ -413,17 +401,17 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_3_ID)) - .thenReturn(DATABASE_3_PRIVILEGED_DTO); + .thenReturn(DATABASE_3_DTO); doThrow(SQLException.class) .when(subsetService) - .getData(eq(DATABASE_3_PRIVILEGED_DTO), any(QueryDto.class), eq(null), eq(null)); - when(subsetService.findById(eq(DATABASE_3_PRIVILEGED_DTO), anyLong())) + .getData(eq(DATABASE_3_DTO), any(QueryDto.class), eq(null), eq(null)); + when(subsetService.findById(eq(DATABASE_3_DTO), anyLong())) .thenReturn(QUERY_5_DTO); when(metadataServiceGateway.getAccess(DATABASE_3_ID, USER_1_ID)) .thenReturn(DATABASE_3_USER_1_READ_ACCESS_DTO); doThrow(SQLException.class) .when(subsetService) - .create(eq(DATABASE_3_PRIVILEGED_DTO), eq(QUERY_5_STATEMENT), any(Instant.class), eq(USER_1_ID)); + .create(eq(DATABASE_3_DTO), eq(QUERY_5_STATEMENT), any(Instant.class), eq(USER_1_ID)); when(httpServletRequest.getMethod()) .thenReturn("POST"); @@ -468,13 +456,11 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_3_ID)) - .thenReturn(DATABASE_3_PRIVILEGED_DTO); - when(subsetService.getData(eq(DATABASE_3_PRIVILEGED_DTO), any(QueryDto.class), eq(0L), eq(10L))) + .thenReturn(DATABASE_3_DTO); + when(subsetService.getData(eq(DATABASE_3_DTO), any(QueryDto.class), eq(0L), eq(10L))) .thenReturn(mock); - when(subsetService.findById(eq(DATABASE_3_PRIVILEGED_DTO), anyLong())) + when(subsetService.findById(eq(DATABASE_3_DTO), anyLong())) .thenReturn(QUERY_5_DTO); - when(schemaService.inspectView(eq(DATABASE_3_PRIVILEGED_DTO), anyString())) - .thenReturn(VIEW_5_DTO); when(httpServletRequest.getMethod()) .thenReturn("POST"); @@ -496,13 +482,11 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_4_ID)) - .thenReturn(DATABASE_4_PRIVILEGED_DTO); - when(subsetService.findById(eq(DATABASE_4_PRIVILEGED_DTO), anyLong())) + .thenReturn(DATABASE_4_DTO); + when(subsetService.findById(eq(DATABASE_4_DTO), anyLong())) .thenReturn(QUERY_5_DTO); - when(subsetService.getData(eq(DATABASE_4_PRIVILEGED_DTO), any(QueryDto.class), eq(0L), eq(10L))) + when(subsetService.getData(eq(DATABASE_4_DTO), any(QueryDto.class), eq(0L), eq(10L))) .thenReturn(mock); - when(schemaService.inspectView(eq(DATABASE_4_PRIVILEGED_DTO), anyString())) - .thenReturn(VIEW_5_DTO); when(httpServletRequest.getMethod()) .thenReturn("POST"); @@ -524,13 +508,11 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); - when(subsetService.findById(eq(DATABASE_1_PRIVILEGED_DTO), anyLong())) + .thenReturn(DATABASE_1_DTO); + when(subsetService.findById(eq(DATABASE_1_DTO), anyLong())) .thenReturn(QUERY_1_DTO); - when(subsetService.getData(eq(DATABASE_1_PRIVILEGED_DTO), any(QueryDto.class), eq(0L), eq(10L))) + when(subsetService.getData(eq(DATABASE_1_DTO), any(QueryDto.class), eq(0L), eq(10L))) .thenReturn(mock); - when(schemaService.inspectView(eq(DATABASE_1_PRIVILEGED_DTO), anyString())) - .thenReturn(VIEW_1_DTO); when(httpServletRequest.getMethod()) .thenReturn("POST"); @@ -550,13 +532,11 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_2_ID)) - .thenReturn(DATABASE_2_PRIVILEGED_DTO); - when(subsetService.findById(eq(DATABASE_2_PRIVILEGED_DTO), anyLong())) + .thenReturn(DATABASE_2_DTO); + when(subsetService.findById(eq(DATABASE_2_DTO), anyLong())) .thenReturn(QUERY_2_DTO); - when(subsetService.getData(eq(DATABASE_2_PRIVILEGED_DTO), any(QueryDto.class), eq(0L), eq(10L))) + when(subsetService.getData(eq(DATABASE_2_DTO), any(QueryDto.class), eq(0L), eq(10L))) .thenReturn(mock); - when(schemaService.inspectView(eq(DATABASE_2_PRIVILEGED_DTO), anyString())) - .thenReturn(VIEW_4_DTO); when(httpServletRequest.getMethod()) .thenReturn("POST"); @@ -575,15 +555,13 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_3_ID)) - .thenReturn(DATABASE_3_PRIVILEGED_DTO); - when(subsetService.findById(DATABASE_3_PRIVILEGED_DTO, QUERY_5_ID)) + .thenReturn(DATABASE_3_DTO); + when(subsetService.findById(DATABASE_3_DTO, QUERY_5_ID)) .thenReturn(QUERY_5_DTO); - when(subsetService.reExecuteCount(DATABASE_3_PRIVILEGED_DTO, QUERY_5_DTO)) + when(subsetService.reExecuteCount(DATABASE_3_DTO, QUERY_5_DTO)) .thenReturn(QUERY_5_RESULT_NUMBER); - when(subsetService.getData(eq(DATABASE_3_PRIVILEGED_DTO), any(QueryDto.class), eq(0L), eq(10L))) + when(subsetService.getData(eq(DATABASE_3_DTO), any(QueryDto.class), eq(0L), eq(10L))) .thenReturn(mock); - when(schemaService.inspectView(eq(DATABASE_3_PRIVILEGED_DTO), anyString())) - .thenReturn(VIEW_5_DTO); when(httpServletRequest.getMethod()) .thenReturn("GET"); @@ -601,10 +579,10 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_3_ID)) - .thenReturn(DATABASE_3_PRIVILEGED_DTO); - when(subsetService.findById(DATABASE_3_PRIVILEGED_DTO, QUERY_5_ID)) + .thenReturn(DATABASE_3_DTO); + when(subsetService.findById(DATABASE_3_DTO, QUERY_5_ID)) .thenReturn(QUERY_5_DTO); - when(subsetService.reExecuteCount(DATABASE_3_PRIVILEGED_DTO, QUERY_5_DTO)) + when(subsetService.reExecuteCount(DATABASE_3_DTO, QUERY_5_DTO)) .thenReturn(QUERY_5_RESULT_NUMBER); when(httpServletRequest.getMethod()) .thenReturn("HEAD"); @@ -627,15 +605,13 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); - when(subsetService.findById(DATABASE_1_PRIVILEGED_DTO, QUERY_1_ID)) + .thenReturn(DATABASE_1_DTO); + when(subsetService.findById(DATABASE_1_DTO, QUERY_1_ID)) .thenReturn(QUERY_1_DTO); - when(subsetService.reExecuteCount(DATABASE_1_PRIVILEGED_DTO, QUERY_1_DTO)) + when(subsetService.reExecuteCount(DATABASE_1_DTO, QUERY_1_DTO)) .thenReturn(QUERY_1_RESULT_NUMBER); - when(subsetService.getData(DATABASE_1_PRIVILEGED_DTO, QUERY_1_DTO, 0L, 10L)) + when(subsetService.getData(DATABASE_1_DTO, QUERY_1_DTO, 0L, 10L)) .thenReturn(mock); - when(schemaService.inspectView(eq(DATABASE_1_PRIVILEGED_DTO), anyString())) - .thenReturn(VIEW_1_DTO); when(httpServletRequest.getMethod()) .thenReturn("GET"); @@ -652,7 +628,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); + .thenReturn(DATABASE_1_DTO); /* test */ assertThrows(NotAllowedException.class, () -> { @@ -667,7 +643,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); + .thenReturn(DATABASE_1_DTO); doThrow(NotAllowedException.class) .when(credentialService) .getAccess(DATABASE_1_ID, USER_1_ID); @@ -687,10 +663,10 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); - when(subsetService.findById(DATABASE_1_PRIVILEGED_DTO, QUERY_1_ID)) + .thenReturn(DATABASE_1_DTO); + when(subsetService.findById(DATABASE_1_DTO, QUERY_1_ID)) .thenReturn(QUERY_1_DTO); - when(subsetService.reExecuteCount(DATABASE_1_PRIVILEGED_DTO, QUERY_1_DTO)) + when(subsetService.reExecuteCount(DATABASE_1_DTO, QUERY_1_DTO)) .thenReturn(QUERY_1_RESULT_NUMBER); when(httpServletRequest.getMethod()) .thenReturn("HEAD"); @@ -711,14 +687,14 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); - when(subsetService.findById(DATABASE_1_PRIVILEGED_DTO, QUERY_1_ID)) + .thenReturn(DATABASE_1_DTO); + when(subsetService.findById(DATABASE_1_DTO, QUERY_1_ID)) .thenReturn(QUERY_1_DTO); when(httpServletRequest.getMethod()) .thenReturn("GET"); doThrow(SQLException.class) .when(subsetService) - .getData(DATABASE_1_PRIVILEGED_DTO, QUERY_1_DTO, 0L, 10L); + .getData(DATABASE_1_DTO, QUERY_1_DTO, 0L, 10L); /* test */ assertThrows(DatabaseUnavailableException.class, () -> { @@ -739,11 +715,11 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { when(credentialService.getAccess(DATABASE_3_ID, USER_3_ID)) .thenReturn(DATABASE_3_USER_3_READ_ACCESS_DTO); when(credentialService.getDatabase(DATABASE_3_ID)) - .thenReturn(DATABASE_3_PRIVILEGED_DTO); + .thenReturn(DATABASE_3_DTO); doNothing() .when(subsetService) - .persist(DATABASE_3_PRIVILEGED_DTO, QUERY_5_ID, true); - when(subsetService.findById(DATABASE_3_PRIVILEGED_DTO, QUERY_5_ID)) + .persist(DATABASE_3_DTO, QUERY_5_ID, true); + when(subsetService.findById(DATABASE_3_DTO, QUERY_5_ID)) .thenReturn(QUERY_5_DTO); /* test */ @@ -812,12 +788,12 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_3_ID)) - .thenReturn(DATABASE_3_PRIVILEGED_DTO); + .thenReturn(DATABASE_3_DTO); when(credentialService.getAccess(DATABASE_3_ID, USER_3_ID)) .thenReturn(DATABASE_3_USER_3_READ_ACCESS_DTO); doThrow(SQLException.class) .when(subsetService) - .persist(DATABASE_3_PRIVILEGED_DTO, QUERY_5_ID, true); + .persist(DATABASE_3_DTO, QUERY_5_ID, true); /* test */ assertThrows(DatabaseUnavailableException.class, () -> { @@ -825,7 +801,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { }); } - protected List<QueryDto> generic_list(Long databaseId, PrivilegedDatabaseDto database, Principal principal) + protected List<QueryDto> generic_list(Long databaseId, DatabaseDto database, Principal principal) throws NotAllowedException, DatabaseUnavailableException, QueryNotFoundException, DatabaseNotFoundException, RemoteUnavailableException, MetadataServiceException { diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/TableEndpointUnitTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/TableEndpointUnitTest.java index e3171892a0..82be231e56 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/TableEndpointUnitTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/TableEndpointUnitTest.java @@ -1,14 +1,14 @@ package at.tuwien.endpoint; import at.tuwien.api.database.DatabaseAccessDto; +import at.tuwien.api.database.DatabaseDto; import at.tuwien.api.database.query.ImportDto; import at.tuwien.api.database.table.*; -import at.tuwien.api.database.table.internal.PrivilegedTableDto; import at.tuwien.endpoints.TableEndpoint; import at.tuwien.exception.*; import at.tuwien.gateway.MetadataServiceGateway; import at.tuwien.service.CredentialService; -import at.tuwien.service.SchemaService; +import at.tuwien.service.DatabaseService; import at.tuwien.service.TableService; import at.tuwien.test.AbstractUnitTest; import jakarta.servlet.http.HttpServletRequest; @@ -61,7 +61,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { private TableService tableService; @MockBean - private SchemaService schemaService; + private DatabaseService databaseService; @MockBean private CredentialService credentialService; @@ -92,16 +92,16 @@ public class TableEndpointUnitTest extends AbstractUnitTest { @Test @WithMockUser(username = USER_LOCAL_ADMIN_USERNAME, authorities = {"system"}) - public void create_succeeds() throws DatabaseUnavailableException, TableMalformedException, + public void create_succeeds() throws DatabaseUnavailableException, TableMalformedException, ViewNotFoundException, DatabaseNotFoundException, TableExistsException, RemoteUnavailableException, SQLException, TableNotFoundException, QueryMalformedException, MetadataServiceException, ContainerNotFoundException { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); - when(tableService.createTable(DATABASE_1_PRIVILEGED_DTO, TABLE_4_CREATE_INTERNAL_DTO)) + .thenReturn(DATABASE_1_DTO); + when(databaseService.createTable(DATABASE_1_DTO, TABLE_4_CREATE_INTERNAL_DTO)) .thenReturn(TABLE_4_DTO); - when(schemaService.inspectTable(DATABASE_1_PRIVILEGED_DTO, TABLE_4_INTERNALNAME)) + when(databaseService.inspectTable(DATABASE_1_DTO, TABLE_4_INTERNALNAME)) .thenReturn(TABLE_4_DTO); /* test */ @@ -142,10 +142,10 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); + .thenReturn(DATABASE_1_DTO); doThrow(SQLException.class) - .when(tableService) - .createTable(DATABASE_1_PRIVILEGED_DTO, TABLE_4_CREATE_INTERNAL_DTO); + .when(databaseService) + .createTable(DATABASE_1_DTO, TABLE_4_CREATE_INTERNAL_DTO); /* test */ assertThrows(DatabaseUnavailableException.class, () -> { @@ -166,12 +166,12 @@ public class TableEndpointUnitTest extends AbstractUnitTest { @Test @WithAnonymousUser public void statistic_succeeds() throws DatabaseUnavailableException, TableNotFoundException, SQLException, - TableMalformedException, RemoteUnavailableException, MetadataServiceException { + TableMalformedException, RemoteUnavailableException, MetadataServiceException, DatabaseNotFoundException { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); - when(tableService.getStatistics(any(PrivilegedTableDto.class))) + .thenReturn(TABLE_8_DTO); + when(tableService.getStatistics(any(DatabaseDto.class), any(TableDto.class))) .thenReturn(TABLE_8_STATISTIC_DTO); /* test */ @@ -186,10 +186,10 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); + .thenReturn(TABLE_8_DTO); doThrow(SQLException.class) .when(tableService) - .getStatistics(any(PrivilegedTableDto.class)); + .getStatistics(any(DatabaseDto.class), any(TableDto.class)); /* test */ assertThrows(DatabaseUnavailableException.class, () -> { @@ -220,10 +220,10 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_1_ID, TABLE_1_ID)) - .thenReturn(TABLE_1_PRIVILEGED_DTO); + .thenReturn(TABLE_1_DTO); doNothing() .when(tableService) - .delete(TABLE_1_PRIVILEGED_DTO); + .delete(TABLE_1_DTO); /* test */ final ResponseEntity<Void> response = tableEndpoint.delete(DATABASE_1_ID, TABLE_1_ID); @@ -263,10 +263,10 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_1_ID, TABLE_1_ID)) - .thenReturn(TABLE_1_PRIVILEGED_DTO); + .thenReturn(TABLE_1_DTO); doThrow(SQLException.class) .when(tableService) - .delete(TABLE_1_PRIVILEGED_DTO); + .delete(TABLE_1_DTO); /* test */ assertThrows(DatabaseUnavailableException.class, () -> { @@ -277,13 +277,14 @@ public class TableEndpointUnitTest extends AbstractUnitTest { @Test @WithAnonymousUser public void getData_succeeds() throws DatabaseUnavailableException, TableNotFoundException, QueryMalformedException, - RemoteUnavailableException, PaginationException, MetadataServiceException, NotAllowedException { + RemoteUnavailableException, PaginationException, MetadataServiceException, NotAllowedException, + DatabaseNotFoundException { final Dataset<Row> mock = sparkSession.emptyDataFrame(); /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); - when(tableService.getData(eq(DATABASE_3_PRIVILEGED_DTO), eq(TABLE_8_INTERNAL_NAME), any(Instant.class), eq(null), eq(null), eq(null), eq(null))) + .thenReturn(TABLE_8_DTO); + when(tableService.getData(eq(DATABASE_3_DTO), eq(TABLE_8_INTERNAL_NAME), any(Instant.class), eq(null), eq(null), eq(null), eq(null))) .thenReturn(mock); when(httpServletRequest.getMethod()) .thenReturn("GET"); @@ -298,15 +299,15 @@ public class TableEndpointUnitTest extends AbstractUnitTest { @WithAnonymousUser public void getData_head_succeeds() throws DatabaseUnavailableException, TableNotFoundException, SQLException, QueryMalformedException, RemoteUnavailableException, PaginationException, - MetadataServiceException, NotAllowedException { + MetadataServiceException, NotAllowedException, DatabaseNotFoundException { final Dataset<Row> mock = sparkSession.emptyDataFrame(); /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); - when(tableService.getCount(eq(TABLE_8_PRIVILEGED_DTO), any(Instant.class))) + .thenReturn(TABLE_8_DTO); + when(tableService.getCount(eq(TABLE_8_DTO), any(Instant.class))) .thenReturn(3L); - when(tableService.getData(eq(DATABASE_3_PRIVILEGED_DTO), eq(TABLE_8_INTERNAL_NAME), any(Instant.class), eq(null), eq(null), eq(null), eq(null))) + when(tableService.getData(eq(DATABASE_3_DTO), eq(TABLE_8_INTERNAL_NAME), any(Instant.class), eq(null), eq(null), eq(null), eq(null))) .thenReturn(mock); when(httpServletRequest.getMethod()) .thenReturn("HEAD"); @@ -328,7 +329,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_1_ID, TABLE_1_ID)) - .thenReturn(TABLE_1_PRIVILEGED_DTO); + .thenReturn(TABLE_1_DTO); /* test */ assertThrows(NotAllowedException.class, () -> { @@ -343,7 +344,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_1_ID, TABLE_1_ID)) - .thenReturn(TABLE_1_PRIVILEGED_DTO); + .thenReturn(TABLE_1_DTO); doThrow(NotAllowedException.class) .when(credentialService) .getAccess(DATABASE_1_ID, USER_2_ID); @@ -361,10 +362,10 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); + .thenReturn(TABLE_8_DTO); doThrow(QueryMalformedException.class) .when(tableService) - .getData(eq(DATABASE_3_PRIVILEGED_DTO), eq(TABLE_8_INTERNAL_NAME), any(Instant.class), eq(null), eq(null), eq(null), eq(null)); + .getData(eq(DATABASE_3_DTO), eq(TABLE_8_INTERNAL_NAME), any(Instant.class), eq(null), eq(null), eq(null), eq(null)); when(httpServletRequest.getMethod()) .thenReturn("GET"); @@ -381,7 +382,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_1_ID, TABLE_1_ID)) - .thenReturn(TABLE_1_PRIVILEGED_DTO); + .thenReturn(TABLE_1_DTO); doThrow(RemoteUnavailableException.class) .when(credentialService) .getAccess(DATABASE_1_ID, USER_2_ID); @@ -397,15 +398,15 @@ public class TableEndpointUnitTest extends AbstractUnitTest { @MethodSource("anyAccess_parameters") public void getData_private_succeeds(String name, DatabaseAccessDto access) throws DatabaseUnavailableException, TableNotFoundException, QueryMalformedException, RemoteUnavailableException, PaginationException, - MetadataServiceException, NotAllowedException { + MetadataServiceException, NotAllowedException, DatabaseNotFoundException { final Dataset<Row> mock = sparkSession.emptyDataFrame(); /* mock */ when(credentialService.getTable(DATABASE_1_ID, TABLE_1_ID)) - .thenReturn(TABLE_1_PRIVILEGED_DTO); + .thenReturn(TABLE_1_DTO); when(credentialService.getAccess(DATABASE_1_ID, USER_2_ID)) .thenReturn(access); - when(tableService.getData(eq(DATABASE_1_PRIVILEGED_DTO), eq(TABLE_1_INTERNAL_NAME), any(Instant.class), eq(null), eq(null), eq(null), eq(null))) + when(tableService.getData(eq(DATABASE_1_DTO), eq(TABLE_1_INTERNAL_NAME), any(Instant.class), eq(null), eq(null), eq(null), eq(null))) .thenReturn(mock); when(httpServletRequest.getMethod()) .thenReturn("GET"); @@ -445,12 +446,12 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); + .thenReturn(TABLE_8_DTO); when(credentialService.getAccess(DATABASE_3_ID, USER_1_ID)) .thenReturn(DATABASE_3_USER_1_WRITE_OWN_ACCESS_DTO); doNothing() .when(tableService) - .createTuple(TABLE_8_PRIVILEGED_DTO, request); + .createTuple(TABLE_8_DTO, request); doNothing() .when(metadataServiceGateway) .updateTableStatistics(DATABASE_3_ID, TABLE_8_ID, TOKEN_ACCESS_TOKEN); @@ -511,7 +512,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); + .thenReturn(TABLE_8_DTO); when(credentialService.getAccess(DATABASE_3_ID, USER_3_ID)) .thenReturn(DATABASE_3_USER_3_READ_ACCESS_DTO); @@ -535,12 +536,12 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); + .thenReturn(TABLE_8_DTO); when(credentialService.getAccess(DATABASE_3_ID, USER_1_ID)) .thenReturn(DATABASE_3_USER_1_WRITE_OWN_ACCESS_DTO); doThrow(SQLException.class) .when(tableService) - .createTuple(TABLE_8_PRIVILEGED_DTO, request); + .createTuple(TABLE_8_DTO, request); /* test */ assertThrows(DatabaseUnavailableException.class, () -> { @@ -562,7 +563,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); + .thenReturn(TABLE_8_DTO); when(credentialService.getAccess(DATABASE_3_ID, USER_1_ID)) .thenReturn(DATABASE_3_USER_1_WRITE_OWN_ACCESS_DTO); @@ -583,7 +584,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); + .thenReturn(TABLE_8_DTO); when(credentialService.getAccess(DATABASE_3_ID, USER_3_ID)) .thenReturn(DATABASE_3_USER_3_WRITE_OWN_ACCESS_DTO); @@ -607,7 +608,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); + .thenReturn(TABLE_8_DTO); when(credentialService.getAccess(DATABASE_3_ID, USER_3_ID)) .thenReturn(DATABASE_3_USER_3_WRITE_ALL_ACCESS_DTO); @@ -632,12 +633,12 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); + .thenReturn(TABLE_8_DTO); when(credentialService.getAccess(DATABASE_3_ID, USER_1_ID)) .thenReturn(DATABASE_3_USER_1_WRITE_OWN_ACCESS_DTO); doNothing() .when(tableService) - .updateTuple(TABLE_8_PRIVILEGED_DTO, request); + .updateTuple(TABLE_8_DTO, request); doNothing() .when(metadataServiceGateway) .updateTableStatistics(DATABASE_3_ID, TABLE_8_ID, TOKEN_ACCESS_TOKEN); @@ -707,7 +708,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); + .thenReturn(TABLE_8_DTO); when(credentialService.getAccess(DATABASE_3_ID, USER_3_ID)) .thenReturn(DATABASE_3_USER_3_READ_ACCESS_DTO); @@ -733,12 +734,12 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); + .thenReturn(TABLE_8_DTO); when(credentialService.getAccess(DATABASE_3_ID, USER_3_ID)) .thenReturn(DATABASE_3_USER_3_WRITE_ALL_ACCESS_DTO); doThrow(SQLException.class) .when(tableService) - .updateTuple(TABLE_8_PRIVILEGED_DTO, request); + .updateTuple(TABLE_8_DTO, request); /* test */ assertThrows(DatabaseUnavailableException.class, () -> { @@ -763,12 +764,12 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); + .thenReturn(TABLE_8_DTO); when(credentialService.getAccess(DATABASE_3_ID, USER_1_ID)) .thenReturn(DATABASE_3_USER_1_WRITE_OWN_ACCESS_DTO); doNothing() .when(tableService) - .updateTuple(TABLE_8_PRIVILEGED_DTO, request); + .updateTuple(TABLE_8_DTO, request); doNothing() .when(metadataServiceGateway) .updateTableStatistics(DATABASE_3_ID, TABLE_8_ID, TOKEN_ACCESS_TOKEN); @@ -794,7 +795,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); + .thenReturn(TABLE_8_DTO); when(credentialService.getAccess(DATABASE_3_ID, USER_3_ID)) .thenReturn(DATABASE_3_USER_3_WRITE_OWN_ACCESS_DTO); @@ -821,12 +822,12 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); + .thenReturn(TABLE_8_DTO); when(credentialService.getAccess(DATABASE_3_ID, USER_3_ID)) .thenReturn(DATABASE_3_USER_3_WRITE_ALL_ACCESS_DTO); doNothing() .when(tableService) - .updateTuple(TABLE_8_PRIVILEGED_DTO, request); + .updateTuple(TABLE_8_DTO, request); doNothing() .when(metadataServiceGateway) .updateTableStatistics(DATABASE_3_ID, TABLE_8_ID, TOKEN_ACCESS_TOKEN); @@ -849,12 +850,12 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); + .thenReturn(TABLE_8_DTO); when(credentialService.getAccess(DATABASE_3_ID, USER_1_ID)) .thenReturn(DATABASE_3_USER_1_WRITE_OWN_ACCESS_DTO); doNothing() .when(tableService) - .deleteTuple(TABLE_8_PRIVILEGED_DTO, request); + .deleteTuple(TABLE_8_DTO, request); doNothing() .when(metadataServiceGateway) .updateTableStatistics(DATABASE_3_ID, TABLE_8_ID, TOKEN_ACCESS_TOKEN); @@ -912,7 +913,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); + .thenReturn(TABLE_8_DTO); when(credentialService.getAccess(DATABASE_3_ID, USER_3_ID)) .thenReturn(DATABASE_3_USER_3_READ_ACCESS_DTO); @@ -934,12 +935,12 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); + .thenReturn(TABLE_8_DTO); when(credentialService.getAccess(DATABASE_3_ID, USER_1_ID)) .thenReturn(DATABASE_3_USER_3_WRITE_OWN_ACCESS_DTO); doThrow(SQLException.class) .when(tableService) - .deleteTuple(TABLE_8_PRIVILEGED_DTO, request); + .deleteTuple(TABLE_8_DTO, request); /* test */ assertThrows(DatabaseUnavailableException.class, () -> { @@ -960,12 +961,12 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); + .thenReturn(TABLE_8_DTO); when(credentialService.getAccess(DATABASE_3_ID, USER_1_ID)) .thenReturn(DATABASE_3_USER_3_WRITE_OWN_ACCESS_DTO); doNothing() .when(tableService) - .deleteTuple(TABLE_8_PRIVILEGED_DTO, request); + .deleteTuple(TABLE_8_DTO, request); doNothing() .when(metadataServiceGateway) .updateTableStatistics(DATABASE_3_ID, TABLE_8_ID, TOKEN_ACCESS_TOKEN); @@ -987,7 +988,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); + .thenReturn(TABLE_8_DTO); when(credentialService.getAccess(DATABASE_3_ID, USER_3_ID)) .thenReturn(DATABASE_3_USER_3_WRITE_OWN_ACCESS_DTO); @@ -1010,12 +1011,12 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); + .thenReturn(TABLE_8_DTO); when(credentialService.getAccess(DATABASE_3_ID, USER_3_ID)) .thenReturn(DATABASE_3_USER_3_WRITE_ALL_ACCESS_DTO); doNothing() .when(tableService) - .deleteTuple(TABLE_8_PRIVILEGED_DTO, request); + .deleteTuple(TABLE_8_DTO, request); doNothing() .when(metadataServiceGateway) .updateTableStatistics(DATABASE_3_ID, TABLE_8_ID, TOKEN_ACCESS_TOKEN); @@ -1032,8 +1033,8 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); - when(tableService.history(TABLE_8_PRIVILEGED_DTO, null)) + .thenReturn(TABLE_8_DTO); + when(tableService.history(TABLE_8_DTO, null)) .thenReturn(List.of()); /* test */ @@ -1048,7 +1049,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_1_ID, TABLE_1_ID)) - .thenReturn(TABLE_1_PRIVILEGED_DTO); + .thenReturn(TABLE_1_DTO); /* test */ assertThrows(NotAllowedException.class, () -> { @@ -1074,7 +1075,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_1_ID, TABLE_1_ID)) - .thenReturn(TABLE_1_PRIVILEGED_DTO); + .thenReturn(TABLE_1_DTO); doThrow(NotAllowedException.class) .when(credentialService) .getAccess(DATABASE_1_ID, USER_4_ID); @@ -1092,10 +1093,10 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_1_ID, TABLE_1_ID)) - .thenReturn(TABLE_1_PRIVILEGED_DTO); + .thenReturn(TABLE_1_DTO); when(credentialService.getAccess(DATABASE_1_ID, USER_2_ID)) .thenReturn(DATABASE_1_USER_2_READ_ACCESS_DTO); - when(tableService.history(TABLE_1_PRIVILEGED_DTO, 10L)) + when(tableService.history(TABLE_1_DTO, 10L)) .thenReturn(List.of()); /* test */ @@ -1110,10 +1111,10 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); + .thenReturn(TABLE_8_DTO); doThrow(SQLException.class) .when(tableService) - .history(TABLE_8_PRIVILEGED_DTO, 100L); + .history(TABLE_8_DTO, 100L); /* test */ assertThrows(DatabaseUnavailableException.class, () -> { @@ -1139,14 +1140,14 @@ public class TableEndpointUnitTest extends AbstractUnitTest { @Test @WithAnonymousUser - public void exportData_succeeds() throws TableNotFoundException, NotAllowedException, - StorageUnavailableException, QueryMalformedException, RemoteUnavailableException, MetadataServiceException { + public void exportData_succeeds() throws TableNotFoundException, NotAllowedException, StorageUnavailableException, + QueryMalformedException, RemoteUnavailableException, MetadataServiceException, DatabaseNotFoundException { final Dataset<Row> mock = sparkSession.emptyDataFrame(); /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); - when(tableService.getData(eq(DATABASE_3_PRIVILEGED_DTO), eq(TABLE_8_INTERNAL_NAME), any(Instant.class), eq(null), eq(null), eq(null), eq(null))) + .thenReturn(TABLE_8_DTO); + when(tableService.getData(eq(DATABASE_3_DTO), eq(TABLE_8_INTERNAL_NAME), any(Instant.class), eq(null), eq(null), eq(null), eq(null))) .thenReturn(mock); /* test */ @@ -1159,15 +1160,15 @@ public class TableEndpointUnitTest extends AbstractUnitTest { @MethodSource("anyAccess_parameters") public void exportData_private_succeeds(String name, DatabaseAccessDto access) throws TableNotFoundException, NotAllowedException, StorageUnavailableException, QueryMalformedException, RemoteUnavailableException, - MetadataServiceException { + MetadataServiceException, DatabaseNotFoundException { final Dataset<Row> mock = sparkSession.emptyDataFrame(); /* mock */ when(credentialService.getTable(DATABASE_1_ID, TABLE_1_ID)) - .thenReturn(TABLE_1_PRIVILEGED_DTO); + .thenReturn(TABLE_1_DTO); when(credentialService.getAccess(DATABASE_1_ID, USER_2_ID)) .thenReturn(access); - when(tableService.getData(eq(DATABASE_1_PRIVILEGED_DTO), eq(TABLE_1_INTERNAL_NAME), any(Instant.class), eq(null), eq(null), eq(null), eq(null))) + when(tableService.getData(eq(DATABASE_1_DTO), eq(TABLE_1_INTERNAL_NAME), any(Instant.class), eq(null), eq(null), eq(null), eq(null))) .thenReturn(mock); /* test */ @@ -1182,7 +1183,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_1_ID, TABLE_1_ID)) - .thenReturn(TABLE_1_PRIVILEGED_DTO); + .thenReturn(TABLE_1_DTO); doThrow(NotAllowedException.class) .when(credentialService) .getAccess(DATABASE_1_ID, USER_4_ID); @@ -1201,8 +1202,8 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_3_ID)) - .thenReturn(DATABASE_3_PRIVILEGED_DTO); - when(tableService.getSchemas(DATABASE_3_PRIVILEGED_DTO)) + .thenReturn(DATABASE_3_DTO); + when(databaseService.exploreTables(DATABASE_3_DTO)) .thenReturn(List.of(TABLE_8_DTO)); /* test */ @@ -1237,10 +1238,10 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_3_ID)) - .thenReturn(DATABASE_3_PRIVILEGED_DTO); + .thenReturn(DATABASE_3_DTO); doThrow(SQLException.class) - .when(tableService) - .getSchemas(DATABASE_3_PRIVILEGED_DTO); + .when(databaseService) + .exploreTables(DATABASE_3_DTO); /* test */ assertThrows(DatabaseUnavailableException.class, () -> { @@ -1260,12 +1261,12 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); + .thenReturn(TABLE_8_DTO); when(credentialService.getAccess(DATABASE_3_ID, USER_1_ID)) .thenReturn(DATABASE_3_USER_1_WRITE_OWN_ACCESS_DTO); doNothing() .when(tableService) - .importDataset(TABLE_8_PRIVILEGED_DTO, request); + .importDataset(TABLE_8_DTO, request); doNothing() .when(metadataServiceGateway) .updateTableStatistics(DATABASE_3_ID, TABLE_8_ID, TOKEN_ACCESS_TOKEN); @@ -1324,12 +1325,12 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); + .thenReturn(TABLE_8_DTO); when(credentialService.getAccess(DATABASE_3_ID, USER_3_ID)) .thenReturn(DATABASE_3_USER_3_WRITE_ALL_ACCESS_DTO); doThrow(SQLException.class) .when(tableService) - .importDataset(any(PrivilegedTableDto.class), eq(request)); + .importDataset(any(TableDto.class), eq(request)); /* test */ assertThrows(DatabaseUnavailableException.class, () -> { @@ -1350,12 +1351,12 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); + .thenReturn(TABLE_8_DTO); when(credentialService.getAccess(DATABASE_3_ID, USER_3_ID)) .thenReturn(DATABASE_3_USER_3_WRITE_OWN_ACCESS_DTO); doThrow(SQLException.class) .when(tableService) - .importDataset(any(PrivilegedTableDto.class), eq(request)); + .importDataset(any(TableDto.class), eq(request)); /* test */ assertThrows(NotAllowedException.class, () -> { @@ -1375,7 +1376,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); + .thenReturn(TABLE_8_DTO); when(credentialService.getAccess(DATABASE_3_ID, USER_3_ID)) .thenReturn(DATABASE_3_USER_3_READ_ACCESS_DTO); @@ -1398,7 +1399,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); + .thenReturn(TABLE_8_DTO); when(credentialService.getAccess(DATABASE_3_ID, USER_1_ID)) .thenReturn(DATABASE_3_USER_1_WRITE_OWN_ACCESS_DTO); @@ -1418,7 +1419,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); + .thenReturn(TABLE_8_DTO); when(credentialService.getAccess(DATABASE_3_ID, USER_3_ID)) .thenReturn(DATABASE_3_USER_3_WRITE_OWN_ACCESS_DTO); @@ -1441,7 +1442,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); + .thenReturn(TABLE_8_DTO); when(credentialService.getAccess(DATABASE_3_ID, USER_1_ID)) .thenReturn(DATABASE_3_USER_3_WRITE_ALL_ACCESS_DTO); @@ -1462,7 +1463,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_1_ID, TABLE_1_ID)) - .thenReturn(TABLE_1_PRIVILEGED_DTO); + .thenReturn(TABLE_1_DTO); when(credentialService.getAccess(DATABASE_1_ID, USER_2_ID)) .thenReturn(DATABASE_1_USER_2_WRITE_ALL_ACCESS_DTO); @@ -1483,7 +1484,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_1_ID, TABLE_2_ID)) - .thenReturn(TABLE_2_PRIVILEGED_DTO); + .thenReturn(TABLE_2_DTO); when(credentialService.getAccess(DATABASE_1_ID, USER_2_ID)) .thenReturn(DATABASE_1_USER_2_WRITE_OWN_ACCESS_DTO); @@ -1503,7 +1504,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_1_ID, TABLE_1_ID)) - .thenReturn(TABLE_1_PRIVILEGED_DTO); + .thenReturn(TABLE_1_DTO); when(credentialService.getAccess(DATABASE_1_ID, USER_2_ID)) .thenReturn(DATABASE_1_USER_2_WRITE_OWN_ACCESS_DTO); @@ -1525,7 +1526,7 @@ public class TableEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getTable(DATABASE_1_ID, TABLE_2_ID)) - .thenReturn(TABLE_2_PRIVILEGED_DTO); + .thenReturn(TABLE_2_DTO); when(credentialService.getAccess(DATABASE_1_ID, USER_2_ID)) .thenReturn(DATABASE_1_USER_2_READ_ACCESS_DTO); diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/ViewEndpointUnitTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/ViewEndpointUnitTest.java index 5f580a2fc1..8586f8f92d 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/ViewEndpointUnitTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/endpoint/ViewEndpointUnitTest.java @@ -4,6 +4,7 @@ import at.tuwien.api.database.ViewDto; import at.tuwien.endpoints.ViewEndpoint; import at.tuwien.exception.*; import at.tuwien.service.CredentialService; +import at.tuwien.service.DatabaseService; import at.tuwien.service.TableService; import at.tuwien.service.ViewService; import at.tuwien.test.AbstractUnitTest; @@ -40,6 +41,9 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { @MockBean private ViewService viewService; + @MockBean + private DatabaseService databaseService; + @MockBean private CredentialService credentialService; @@ -67,8 +71,8 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); - when(viewService.create(DATABASE_1_PRIVILEGED_DTO, VIEW_1_CREATE_DTO)) + .thenReturn(DATABASE_1_DTO); + when(databaseService.createView(DATABASE_1_DTO, VIEW_1_CREATE_DTO)) .thenReturn(VIEW_1_DTO); /* test */ @@ -83,10 +87,10 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); + .thenReturn(DATABASE_1_DTO); doThrow(SQLException.class) - .when(viewService) - .create(DATABASE_1_PRIVILEGED_DTO, VIEW_1_CREATE_DTO); + .when(databaseService) + .createView(DATABASE_1_DTO, VIEW_1_CREATE_DTO); /* test */ assertThrows(DatabaseUnavailableException.class, () -> { @@ -101,8 +105,8 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); - when(viewService.create(DATABASE_1_PRIVILEGED_DTO, VIEW_1_CREATE_DTO)) + .thenReturn(DATABASE_1_DTO); + when(databaseService.createView(DATABASE_1_DTO, VIEW_1_CREATE_DTO)) .thenReturn(VIEW_1_DTO); /* test */ @@ -134,8 +138,8 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); - when(viewService.getSchemas(DATABASE_1_PRIVILEGED_DTO)) + .thenReturn(DATABASE_1_DTO); + when(databaseService.exploreViews(DATABASE_1_DTO)) .thenReturn(List.of(VIEW_1_DTO, VIEW_2_DTO, VIEW_3_DTO)); /* test */ @@ -176,10 +180,10 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); + .thenReturn(DATABASE_1_DTO); doThrow(SQLException.class) - .when(viewService) - .getSchemas(DATABASE_1_PRIVILEGED_DTO); + .when(databaseService) + .exploreViews(DATABASE_1_DTO); /* test */ assertThrows(DatabaseUnavailableException.class, () -> { @@ -204,10 +208,10 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getView(DATABASE_1_ID, VIEW_1_ID)) - .thenReturn(VIEW_1_PRIVILEGED_DTO); + .thenReturn(VIEW_1_DTO); doNothing() .when(viewService) - .delete(DATABASE_1_PRIVILEGED_DTO, VIEW_1_INTERNAL_NAME); + .delete(VIEW_1_DTO); /* test */ final ResponseEntity<Void> response = viewEndpoint.delete(DATABASE_1_ID, VIEW_1_ID); @@ -221,10 +225,10 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getView(DATABASE_1_ID, VIEW_1_ID)) - .thenReturn(VIEW_1_PRIVILEGED_DTO); + .thenReturn(VIEW_1_DTO); doThrow(SQLException.class) .when(viewService) - .delete(DATABASE_1_PRIVILEGED_DTO, VIEW_1_INTERNAL_NAME); + .delete(VIEW_1_DTO); /* test */ assertThrows(DatabaseUnavailableException.class, () -> { @@ -239,10 +243,10 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getDatabase(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); + .thenReturn(DATABASE_1_DTO); doNothing() .when(viewService) - .delete(DATABASE_1_PRIVILEGED_DTO, VIEW_1_INTERNAL_NAME); + .delete(VIEW_1_DTO); /* test */ assertThrows(org.springframework.security.access.AccessDeniedException.class, () -> { @@ -268,17 +272,17 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { @Test @WithMockUser(username = USER_1_USERNAME, authorities = {"view-database-view-data"}) - public void getData_private_succeeds() throws RemoteUnavailableException, ViewNotFoundException, ViewMalformedException, + public void getData_private_succeeds() throws RemoteUnavailableException, ViewNotFoundException, SQLException, DatabaseUnavailableException, QueryMalformedException, PaginationException, - NotAllowedException, MetadataServiceException, TableNotFoundException { + NotAllowedException, MetadataServiceException, TableNotFoundException, DatabaseNotFoundException { final Dataset<Row> mock = sparkSession.emptyDataFrame(); /* mock */ when(credentialService.getView(DATABASE_1_ID, VIEW_1_ID)) - .thenReturn(VIEW_1_PRIVILEGED_DTO); + .thenReturn(VIEW_1_DTO); when(credentialService.getAccess(DATABASE_1_ID, USER_1_ID)) .thenReturn(DATABASE_1_USER_1_READ_ACCESS_DTO); - when(tableService.getData(eq(DATABASE_1_PRIVILEGED_DTO), eq(VIEW_1_INTERNAL_NAME), any(Instant.class), eq(0L), eq(10L), eq(null), eq(null))) + when(tableService.getData(eq(DATABASE_1_DTO), eq(VIEW_1_INTERNAL_NAME), any(Instant.class), eq(0L), eq(10L), eq(null), eq(null))) .thenReturn(mock); when(httpServletRequest.getMethod()) .thenReturn("GET"); @@ -293,16 +297,16 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { @WithMockUser(username = USER_1_USERNAME, authorities = {"view-database-view-data"}) public void getData_privateHead_succeeds() throws RemoteUnavailableException, ViewNotFoundException, SQLException, DatabaseUnavailableException, QueryMalformedException, PaginationException, - NotAllowedException, MetadataServiceException, TableNotFoundException { + NotAllowedException, MetadataServiceException, TableNotFoundException, DatabaseNotFoundException { /* mock */ when(credentialService.getView(DATABASE_1_ID, VIEW_3_ID)) - .thenReturn(VIEW_3_PRIVILEGED_DTO); + .thenReturn(VIEW_3_DTO); when(credentialService.getAccess(DATABASE_1_ID, USER_1_ID)) .thenReturn(DATABASE_1_USER_1_READ_ACCESS_DTO); when(httpServletRequest.getMethod()) .thenReturn("HEAD"); - when(viewService.count(eq(VIEW_3_PRIVILEGED_DTO), any(Instant.class))) + when(viewService.count(eq(VIEW_3_DTO), any(Instant.class))) .thenReturn(VIEW_3_DATA_COUNT); /* test */ @@ -324,7 +328,7 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getView(DATABASE_1_ID, VIEW_3_ID)) - .thenReturn(VIEW_3_PRIVILEGED_DTO); + .thenReturn(VIEW_3_DTO); when(httpServletRequest.getMethod()) .thenReturn("GET"); doThrow(NotAllowedException.class) @@ -360,7 +364,7 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getView(DATABASE_1_ID, VIEW_3_ID)) - .thenReturn(VIEW_3_PRIVILEGED_DTO); + .thenReturn(VIEW_3_DTO); doThrow(NotAllowedException.class) .when(credentialService) .getAccess(DATABASE_1_ID, USER_3_ID); @@ -378,7 +382,7 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getView(DATABASE_1_ID, VIEW_3_ID)) - .thenReturn(VIEW_3_PRIVILEGED_DTO); + .thenReturn(VIEW_3_DTO); doThrow(NotAllowedException.class) .when(credentialService) .getAccess(DATABASE_1_ID, USER_3_ID); @@ -412,7 +416,7 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { /* mock */ when(credentialService.getView(DATABASE_1_ID, VIEW_3_ID)) - .thenReturn(VIEW_3_PRIVILEGED_DTO); + .thenReturn(VIEW_3_DTO); doThrow(NotAllowedException.class) .when(credentialService) .getAccess(DATABASE_1_ID, USER_1_ID); diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/gateway/MetadataServiceGatewayUnitTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/gateway/MetadataServiceGatewayUnitTest.java index f65de0707e..a8633836b8 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/gateway/MetadataServiceGatewayUnitTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/gateway/MetadataServiceGatewayUnitTest.java @@ -1,16 +1,12 @@ package at.tuwien.gateway; import at.tuwien.api.container.ContainerDto; -import at.tuwien.api.container.internal.PrivilegedContainerDto; import at.tuwien.api.database.DatabaseAccessDto; +import at.tuwien.api.database.DatabaseDto; import at.tuwien.api.database.ViewDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; -import at.tuwien.api.database.internal.PrivilegedViewDto; import at.tuwien.api.database.table.TableDto; -import at.tuwien.api.database.table.internal.PrivilegedTableDto; import at.tuwien.api.identifier.IdentifierBriefDto; import at.tuwien.api.user.UserDto; -import at.tuwien.api.user.internal.PrivilegedUserDto; import at.tuwien.exception.*; import at.tuwien.test.AbstractUnitTest; import lombok.extern.log4j.Log4j2; @@ -75,13 +71,13 @@ public class MetadataServiceGatewayUnitTest extends AbstractUnitTest { .body(TABLE_1_DTO)); /* test */ - final PrivilegedTableDto response = metadataServiceGateway.getTableById(DATABASE_1_ID, TABLE_1_ID); - assertEquals(IMAGE_1_JDBC, response.getDatabase().getContainer().getImage().getJdbcMethod()); - assertEquals(CONTAINER_1_HOST, response.getDatabase().getContainer().getHost()); - assertEquals(CONTAINER_1_PORT, response.getDatabase().getContainer().getPort()); - assertEquals(CONTAINER_1_PRIVILEGED_USERNAME, response.getDatabase().getContainer().getUsername()); - assertEquals(CONTAINER_1_PRIVILEGED_PASSWORD, response.getDatabase().getContainer().getPassword()); - assertEquals(DATABASE_1_INTERNALNAME, response.getDatabase().getInternalName()); + final TableDto response = metadataServiceGateway.getTableById(DATABASE_1_ID, TABLE_1_ID); + assertEquals(IMAGE_1_JDBC, response.getJdbcMethod()); + assertEquals(CONTAINER_1_HOST, response.getHost()); + assertEquals(CONTAINER_1_PORT, response.getPort()); + assertEquals(CONTAINER_1_PRIVILEGED_USERNAME, response.getUsername()); + assertEquals(CONTAINER_1_PRIVILEGED_PASSWORD, response.getPassword()); + assertEquals(DATABASE_1_INTERNALNAME, response.getDatabase()); assertEquals(TABLE_1_INTERNAL_NAME, response.getInternalName()); } @@ -181,13 +177,13 @@ public class MetadataServiceGatewayUnitTest extends AbstractUnitTest { headers.set("X-Port", "" + CONTAINER_1_PORT); /* mock */ - when(internalRestTemplate.exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(PrivilegedDatabaseDto.class))) + when(internalRestTemplate.exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(DatabaseDto.class))) .thenReturn(ResponseEntity.ok() .headers(headers) - .body(DATABASE_1_PRIVILEGED_DTO)); + .body(DATABASE_1_DTO)); /* test */ - final PrivilegedDatabaseDto response = metadataServiceGateway.getDatabaseById(DATABASE_1_ID); + final DatabaseDto response = metadataServiceGateway.getDatabaseById(DATABASE_1_ID); assertEquals(DATABASE_1_ID, response.getId()); } @@ -197,7 +193,7 @@ public class MetadataServiceGatewayUnitTest extends AbstractUnitTest { /* mock */ doThrow(HttpServerErrorException.class) .when(internalRestTemplate) - .exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(PrivilegedDatabaseDto.class)); + .exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(DatabaseDto.class)); /* test */ assertThrows(RemoteUnavailableException.class, () -> { @@ -211,7 +207,7 @@ public class MetadataServiceGatewayUnitTest extends AbstractUnitTest { /* mock */ doThrow(HttpClientErrorException.NotFound.class) .when(internalRestTemplate) - .exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(PrivilegedDatabaseDto.class)); + .exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(DatabaseDto.class)); /* test */ assertThrows(DatabaseNotFoundException.class, () -> { @@ -223,7 +219,7 @@ public class MetadataServiceGatewayUnitTest extends AbstractUnitTest { public void getDatabaseById_statusCode_fails() { /* mock */ - when(internalRestTemplate.exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(PrivilegedDatabaseDto.class))) + when(internalRestTemplate.exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(DatabaseDto.class))) .thenReturn(ResponseEntity.status(HttpStatus.NO_CONTENT) .build()); @@ -240,7 +236,7 @@ public class MetadataServiceGatewayUnitTest extends AbstractUnitTest { headers.set("X-Password", CONTAINER_1_PRIVILEGED_PASSWORD); /* mock */ - when(internalRestTemplate.exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(PrivilegedDatabaseDto.class))) + when(internalRestTemplate.exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(DatabaseDto.class))) .thenReturn(ResponseEntity.status(HttpStatus.OK) .headers(headers) .build()); @@ -261,7 +257,7 @@ public class MetadataServiceGatewayUnitTest extends AbstractUnitTest { headers.add(customHeaders.get(j), ""); } /* mock */ - when(internalRestTemplate.exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(PrivilegedDatabaseDto.class))) + when(internalRestTemplate.exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(DatabaseDto.class))) .thenReturn(ResponseEntity.status(HttpStatus.OK) .headers(headers) .build()); @@ -285,7 +281,7 @@ public class MetadataServiceGatewayUnitTest extends AbstractUnitTest { .body(CONTAINER_1_DTO)); /* test */ - final PrivilegedContainerDto response = metadataServiceGateway.getContainerById(CONTAINER_1_ID); + final ContainerDto response = metadataServiceGateway.getContainerById(CONTAINER_1_ID); assertEquals(CONTAINER_1_ID, response.getId()); } @@ -388,7 +384,7 @@ public class MetadataServiceGatewayUnitTest extends AbstractUnitTest { .body(VIEW_1_DTO)); /* test */ - final PrivilegedViewDto response = metadataServiceGateway.getViewById(CONTAINER_1_ID, VIEW_1_ID); + final ViewDto response = metadataServiceGateway.getViewById(CONTAINER_1_ID, VIEW_1_ID); assertEquals(VIEW_1_ID, response.getId()); } @@ -477,19 +473,6 @@ public class MetadataServiceGatewayUnitTest extends AbstractUnitTest { }); } - @Test - public void getUserById_succeeds() throws RemoteUnavailableException, UserNotFoundException, MetadataServiceException { - - /* mock */ - when(internalRestTemplate.exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(UserDto.class))) - .thenReturn(ResponseEntity.ok() - .body(USER_1_DTO)); - - /* test */ - final UserDto response = metadataServiceGateway.getUserById(USER_1_ID); - assertEquals(USER_1_ID, response.getId()); - } - @Test public void getUserById_unavailable_fails() { @@ -505,49 +488,7 @@ public class MetadataServiceGatewayUnitTest extends AbstractUnitTest { } @Test - public void getUserById_notFound_fails() { - - /* mock */ - doThrow(HttpClientErrorException.NotFound.class) - .when(internalRestTemplate) - .exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(UserDto.class)); - - /* test */ - assertThrows(UserNotFoundException.class, () -> { - metadataServiceGateway.getUserById(USER_1_ID); - }); - } - - @Test - public void getUserById_statusCode_fails() { - - /* mock */ - when(internalRestTemplate.exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(UserDto.class))) - .thenReturn(ResponseEntity.status(HttpStatus.NO_CONTENT) - .build()); - - /* test */ - assertThrows(MetadataServiceException.class, () -> { - metadataServiceGateway.getUserById(USER_1_ID); - }); - } - - @Test - public void getUserById_emptyBody_fails() { - - /* mock */ - when(internalRestTemplate.exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(UserDto.class))) - .thenReturn(ResponseEntity.ok() - .build()); - - /* test */ - assertThrows(MetadataServiceException.class, () -> { - metadataServiceGateway.getUserById(USER_1_ID); - }); - } - - @Test - public void getPrivilegedUserById_succeeds() throws RemoteUnavailableException, UserNotFoundException, + public void getUserById_succeeds() throws RemoteUnavailableException, UserNotFoundException, MetadataServiceException { final HttpHeaders headers = new HttpHeaders(); headers.set("X-Username", CONTAINER_1_PRIVILEGED_USERNAME); @@ -560,28 +501,14 @@ public class MetadataServiceGatewayUnitTest extends AbstractUnitTest { .body(USER_1_DTO)); /* test */ - final PrivilegedUserDto response = metadataServiceGateway.getPrivilegedUserById(USER_1_ID); + final UserDto response = metadataServiceGateway.getUserById(USER_1_ID); assertEquals(USER_1_ID, response.getId()); assertEquals(CONTAINER_1_PRIVILEGED_USERNAME, response.getUsername()); assertEquals(CONTAINER_1_PRIVILEGED_PASSWORD, response.getPassword()); } @Test - public void getPrivilegedUserById_unavailable_fails() { - - /* mock */ - doThrow(HttpServerErrorException.class) - .when(internalRestTemplate) - .exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(UserDto.class)); - - /* test */ - assertThrows(RemoteUnavailableException.class, () -> { - metadataServiceGateway.getPrivilegedUserById(USER_1_ID); - }); - } - - @Test - public void getPrivilegedUserById_notFound_fails() { + public void getUserById_notFound_fails() { /* mock */ doThrow(HttpClientErrorException.NotFound.class) @@ -590,12 +517,12 @@ public class MetadataServiceGatewayUnitTest extends AbstractUnitTest { /* test */ assertThrows(UserNotFoundException.class, () -> { - metadataServiceGateway.getPrivilegedUserById(USER_1_ID); + metadataServiceGateway.getUserById(USER_1_ID); }); } @Test - public void getPrivilegedUserById_statusCode_fails() { + public void getUserById_statusCode_fails() { /* mock */ when(internalRestTemplate.exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(UserDto.class))) @@ -604,12 +531,12 @@ public class MetadataServiceGatewayUnitTest extends AbstractUnitTest { /* test */ assertThrows(MetadataServiceException.class, () -> { - metadataServiceGateway.getPrivilegedUserById(USER_1_ID); + metadataServiceGateway.getUserById(USER_1_ID); }); } @Test - public void getPrivilegedUserById_headerMissing_fails() { + public void getUserById_headerMissing_fails() { final List<String> customHeaders = List.of("X-Username", "X-Password"); for (int i = 0; i < customHeaders.size(); i++) { @@ -624,13 +551,13 @@ public class MetadataServiceGatewayUnitTest extends AbstractUnitTest { /* test */ assertThrows(MetadataServiceException.class, () -> { - metadataServiceGateway.getPrivilegedUserById(USER_1_ID); + metadataServiceGateway.getUserById(USER_1_ID); }); } } @Test - public void getPrivilegedUserById_emptyBody_fails() { + public void getUserById_emptyBody_fails() { final HttpHeaders headers = new HttpHeaders(); headers.set("X-Username", CONTAINER_1_PRIVILEGED_USERNAME); headers.set("X-Password", CONTAINER_1_PRIVILEGED_PASSWORD); @@ -643,7 +570,7 @@ public class MetadataServiceGatewayUnitTest extends AbstractUnitTest { /* test */ assertThrows(MetadataServiceException.class, () -> { - metadataServiceGateway.getPrivilegedUserById(USER_1_ID); + metadataServiceGateway.getUserById(USER_1_ID); }); } diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/listener/DefaultListenerIntegrationTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/listener/DefaultListenerIntegrationTest.java index 88bad7b061..44e5f88912 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/listener/DefaultListenerIntegrationTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/listener/DefaultListenerIntegrationTest.java @@ -57,8 +57,8 @@ public class DefaultListenerIntegrationTest extends AbstractUnitTest { public void beforeEach() throws SQLException { genesis(); /* database */ - MariaDbConfig.dropAllDatabases(CONTAINER_1_PRIVILEGED_DTO); - MariaDbConfig.createInitDatabase(CONTAINER_1_PRIVILEGED_DTO, DATABASE_1_DTO); + MariaDbConfig.dropAllDatabases(CONTAINER_1_DTO); + MariaDbConfig.createInitDatabase(CONTAINER_1_DTO, DATABASE_1_DTO); } @Test @@ -68,7 +68,7 @@ public class DefaultListenerIntegrationTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getTableById(DATABASE_1_ID, TABLE_1_ID)) - .thenReturn(TABLE_1_PRIVILEGED_DTO); + .thenReturn(TABLE_1_DTO); /* test */ defaultListener.onMessage(request); diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/listener/DefaultListenerUnitTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/listener/DefaultListenerUnitTest.java index 648e36caa9..b3a3bf6397 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/listener/DefaultListenerUnitTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/listener/DefaultListenerUnitTest.java @@ -52,8 +52,8 @@ public class DefaultListenerUnitTest extends AbstractUnitTest { @BeforeEach public void beforeEach() throws SQLException { /* metadata database */ - MariaDbConfig.dropAllDatabases(CONTAINER_1_PRIVILEGED_DTO); - MariaDbConfig.createInitDatabase(CONTAINER_1_PRIVILEGED_DTO, DATABASE_1_DTO); + MariaDbConfig.dropAllDatabases(CONTAINER_1_DTO); + MariaDbConfig.createInitDatabase(CONTAINER_1_DTO, DATABASE_1_DTO); } @Test @@ -81,7 +81,7 @@ public class DefaultListenerUnitTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getTableById(DATABASE_1_ID, TABLE_1_ID)) - .thenReturn(TABLE_1_PRIVILEGED_DTO); + .thenReturn(TABLE_1_DTO); /* test */ defaultListener.onMessage(request); diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/SubsetEndpointMvcTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/SubsetEndpointMvcTest.java index 158c6743a4..abbe28d55a 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/SubsetEndpointMvcTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/SubsetEndpointMvcTest.java @@ -41,8 +41,8 @@ public class SubsetEndpointMvcTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getDatabaseById(DATABASE_3_ID)) - .thenReturn(DATABASE_3_PRIVILEGED_DTO); - when(subsetService.findById(DATABASE_3_PRIVILEGED_DTO, QUERY_5_ID)) + .thenReturn(DATABASE_3_DTO); + when(subsetService.findById(DATABASE_3_DTO, QUERY_5_ID)) .thenReturn(QUERY_5_DTO); /* test */ @@ -56,8 +56,8 @@ public class SubsetEndpointMvcTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getDatabaseById(DATABASE_3_ID)) - .thenReturn(DATABASE_3_PRIVILEGED_DTO); - when(subsetService.findById(DATABASE_3_PRIVILEGED_DTO, QUERY_5_ID)) + .thenReturn(DATABASE_3_DTO); + when(subsetService.findById(DATABASE_3_DTO, QUERY_5_ID)) .thenReturn(QUERY_5_DTO); /* test */ @@ -72,8 +72,8 @@ public class SubsetEndpointMvcTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getDatabaseById(DATABASE_4_ID)) - .thenReturn(DATABASE_4_PRIVILEGED_DTO); - when(subsetService.findById(DATABASE_4_PRIVILEGED_DTO, QUERY_7_ID)) + .thenReturn(DATABASE_4_DTO); + when(subsetService.findById(DATABASE_4_DTO, QUERY_7_ID)) .thenReturn(QUERY_5_DTO); /* test */ diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/AccessServiceIntegrationTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/AccessServiceIntegrationTest.java index 5eccf50ed2..bc121c3b26 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/AccessServiceIntegrationTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/AccessServiceIntegrationTest.java @@ -44,16 +44,16 @@ public class AccessServiceIntegrationTest extends AbstractUnitTest { public void beforeEach() throws SQLException { genesis(); /* metadata database */ - MariaDbConfig.dropDatabase(CONTAINER_1_PRIVILEGED_DTO, DATABASE_1_INTERNALNAME); - MariaDbConfig.createInitDatabase(CONTAINER_1_PRIVILEGED_DTO, DATABASE_1_DTO); + MariaDbConfig.dropDatabase(CONTAINER_1_DTO, DATABASE_1_INTERNALNAME); + MariaDbConfig.createInitDatabase(CONTAINER_1_DTO, DATABASE_1_DTO); } @Test public void create_read_succeeds() throws SQLException, DatabaseMalformedException { /* test */ - accessService.create(DATABASE_1_PRIVILEGED_DTO, USER_1_PRIVILEGED_DTO, AccessTypeDto.READ); - final List<String> privileges = MariaDbConfig.getPrivileges(DATABASE_1_PRIVILEGED_DTO, USER_1_USERNAME); + accessService.create(DATABASE_1_DTO, USER_1_DTO, AccessTypeDto.READ); + final List<String> privileges = MariaDbConfig.getPrivileges(DATABASE_1_DTO, USER_1_USERNAME); for (String privilege : grantDefaultRead.split(",")) { assertTrue(privileges.stream().anyMatch(p -> p.trim().equals(privilege.trim()))); } @@ -63,8 +63,8 @@ public class AccessServiceIntegrationTest extends AbstractUnitTest { public void create_writeOwn_succeeds() throws SQLException, DatabaseMalformedException { /* test */ - accessService.create(DATABASE_1_PRIVILEGED_DTO, USER_1_PRIVILEGED_DTO, AccessTypeDto.WRITE_OWN); - final List<String> privileges = MariaDbConfig.getPrivileges(DATABASE_1_PRIVILEGED_DTO, USER_1_USERNAME); + accessService.create(DATABASE_1_DTO, USER_1_DTO, AccessTypeDto.WRITE_OWN); + final List<String> privileges = MariaDbConfig.getPrivileges(DATABASE_1_DTO, USER_1_USERNAME); for (String privilege : grantDefaultWrite.split(",")) { assertTrue(privileges.stream().anyMatch(p -> p.trim().equals(privilege.trim()))); } @@ -74,8 +74,8 @@ public class AccessServiceIntegrationTest extends AbstractUnitTest { public void create_writeAll_succeeds() throws SQLException, DatabaseMalformedException { /* test */ - accessService.create(DATABASE_1_PRIVILEGED_DTO, USER_1_PRIVILEGED_DTO, AccessTypeDto.WRITE_ALL); - final List<String> privileges = MariaDbConfig.getPrivileges(DATABASE_1_PRIVILEGED_DTO, USER_1_USERNAME); + accessService.create(DATABASE_1_DTO, USER_1_DTO, AccessTypeDto.WRITE_ALL); + final List<String> privileges = MariaDbConfig.getPrivileges(DATABASE_1_DTO, USER_1_USERNAME); for (String privilege : grantDefaultWrite.split(",")) { assertTrue(privileges.stream().anyMatch(p -> p.trim().equals(privilege.trim()))); } @@ -85,8 +85,8 @@ public class AccessServiceIntegrationTest extends AbstractUnitTest { public void update_read_succeeds() throws SQLException, DatabaseMalformedException { /* test */ - accessService.update(DATABASE_1_PRIVILEGED_DTO, USER_1_PRIVILEGED_DTO, AccessTypeDto.READ); - final List<String> privileges = MariaDbConfig.getPrivileges(DATABASE_1_PRIVILEGED_DTO, USER_1_USERNAME); + accessService.update(DATABASE_1_DTO, USER_1_DTO, AccessTypeDto.READ); + final List<String> privileges = MariaDbConfig.getPrivileges(DATABASE_1_DTO, USER_1_USERNAME); for (String privilege : grantDefaultRead.split(",")) { assertTrue(privileges.stream().anyMatch(p -> p.trim().equals(privilege.trim()))); } @@ -96,8 +96,8 @@ public class AccessServiceIntegrationTest extends AbstractUnitTest { public void update_writeOwn_succeeds() throws SQLException, DatabaseMalformedException { /* test */ - accessService.update(DATABASE_1_PRIVILEGED_DTO, USER_1_PRIVILEGED_DTO, AccessTypeDto.WRITE_OWN); - final List<String> privileges = MariaDbConfig.getPrivileges(DATABASE_1_PRIVILEGED_DTO, USER_1_USERNAME); + accessService.update(DATABASE_1_DTO, USER_1_DTO, AccessTypeDto.WRITE_OWN); + final List<String> privileges = MariaDbConfig.getPrivileges(DATABASE_1_DTO, USER_1_USERNAME); for (String privilege : grantDefaultWrite.split(",")) { assertTrue(privileges.stream().anyMatch(p -> p.trim().equals(privilege.trim()))); } @@ -107,8 +107,8 @@ public class AccessServiceIntegrationTest extends AbstractUnitTest { public void update_writeAll_succeeds() throws SQLException, DatabaseMalformedException { /* test */ - accessService.update(DATABASE_1_PRIVILEGED_DTO, USER_1_PRIVILEGED_DTO, AccessTypeDto.WRITE_ALL); - final List<String> privileges = MariaDbConfig.getPrivileges(DATABASE_1_PRIVILEGED_DTO, USER_1_USERNAME); + accessService.update(DATABASE_1_DTO, USER_1_DTO, AccessTypeDto.WRITE_ALL); + final List<String> privileges = MariaDbConfig.getPrivileges(DATABASE_1_DTO, USER_1_USERNAME); for (String privilege : grantDefaultWrite.split(",")) { assertTrue(privileges.stream().anyMatch(p -> p.trim().equals(privilege.trim()))); } @@ -119,7 +119,7 @@ public class AccessServiceIntegrationTest extends AbstractUnitTest { /* test */ assertThrows(DatabaseMalformedException.class, () -> { - accessService.update(DATABASE_1_PRIVILEGED_DTO, USER_5_PRIVILEGED_DTO, AccessTypeDto.WRITE_ALL); + accessService.update(DATABASE_1_DTO, USER_5_DTO, AccessTypeDto.WRITE_ALL); }); } @@ -127,8 +127,8 @@ public class AccessServiceIntegrationTest extends AbstractUnitTest { public void delete_succeeds() throws SQLException, DatabaseMalformedException { /* test */ - accessService.delete(DATABASE_1_PRIVILEGED_DTO, USER_1_PRIVILEGED_DTO); - final List<String> privileges = MariaDbConfig.getPrivileges(DATABASE_1_PRIVILEGED_DTO, USER_1_USERNAME); + accessService.delete(DATABASE_1_DTO, USER_1_DTO); + final List<String> privileges = MariaDbConfig.getPrivileges(DATABASE_1_DTO, USER_1_USERNAME); assertEquals(1, privileges.size()); assertEquals("USAGE", privileges.get(0)); } @@ -138,7 +138,7 @@ public class AccessServiceIntegrationTest extends AbstractUnitTest { /* test */ assertThrows(DatabaseMalformedException.class, () -> { - accessService.delete(DATABASE_1_PRIVILEGED_DTO, USER_5_PRIVILEGED_DTO); + accessService.delete(DATABASE_1_DTO, USER_5_DTO); }); } diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/ContainerServiceIntegrationTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/ContainerServiceIntegrationTest.java new file mode 100644 index 0000000000..e793a8362c --- /dev/null +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/ContainerServiceIntegrationTest.java @@ -0,0 +1,109 @@ +package at.tuwien.service; + +import at.tuwien.api.database.DatabaseDto; +import at.tuwien.config.MariaDbConfig; +import at.tuwien.config.MariaDbContainerConfig; +import at.tuwien.exception.DatabaseMalformedException; +import at.tuwien.exception.QueryStoreCreateException; +import at.tuwien.test.AbstractUnitTest; +import lombok.extern.log4j.Log4j2; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.testcontainers.containers.MariaDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.sql.SQLException; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +@Log4j2 +@SpringBootTest +@ExtendWith(SpringExtension.class) +@Testcontainers +public class ContainerServiceIntegrationTest extends AbstractUnitTest { + + @Autowired + private ContainerService containerService; + + @Container + private static MariaDBContainer<?> mariaDBContainer = MariaDbContainerConfig.getContainer(); + + @BeforeAll + public static void beforeAll() throws InterruptedException { + Thread.sleep(1000) /* wait for test container some more */; + } + + @BeforeEach + public void beforeEach() throws SQLException { + genesis(); + /* metadata database */ + MariaDbConfig.dropDatabase(CONTAINER_1_DTO, DATABASE_1_INTERNALNAME); + } + + @Test + public void create_succeeds() throws SQLException, DatabaseMalformedException { + + /* test */ + final DatabaseDto response = containerService.createDatabase(CONTAINER_1_DTO, DATABASE_1_CREATE_INTERNAL); + assertNull(response.getName()); + assertEquals(DATABASE_1_INTERNALNAME, response.getInternalName()); + assertEquals(EXCHANGE_DBREPO_NAME, response.getExchangeName()); + assertNotNull(response.getOwner()); + assertEquals(USER_1_ID, response.getOwner().getId()); + assertNotNull(response.getContact()); + assertEquals(USER_1_ID, response.getContact().getId()); + assertNotNull(response.getContainer()); + assertEquals(CONTAINER_1_ID, response.getContainer().getId()); + } + + @Test + public void create_exists_fails() throws SQLException { + + /* mock */ + MariaDbConfig.createDatabase(CONTAINER_1_DTO, DATABASE_1_INTERNALNAME); + + /* test */ + assertThrows(DatabaseMalformedException.class, () -> { + containerService.createDatabase(CONTAINER_1_DTO, DATABASE_1_CREATE_INTERNAL); + }); + } + + @Test + public void createQueryStore_succeeds() throws SQLException, QueryStoreCreateException, InterruptedException { + + /* mock */ + MariaDbConfig.dropQueryStore(DATABASE_1_DTO); + + /* test */ + createQueryStore_generic(DATABASE_1_INTERNALNAME); + } + + @Test + public void createQueryStore_fails() { + + /* test */ + assertThrows(QueryStoreCreateException.class, () -> { + createQueryStore_generic(DATABASE_1_INTERNALNAME); + }); + } + + protected void createQueryStore_generic(String databaseName) throws SQLException, QueryStoreCreateException, + InterruptedException { + + /* pre-condition */ + Thread.sleep(1000) /* wait for test container some more */; + + /* test */ + containerService.createQueryStore(CONTAINER_1_DTO, databaseName); + final List<Map<String, Object>> response = MariaDbConfig.listQueryStore(DATABASE_1_DTO); + assertEquals(0, response.size()); + } +} diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/CredentialServiceUnitTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/CredentialServiceUnitTest.java index 160918bfaa..8b5e2cc7c5 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/CredentialServiceUnitTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/CredentialServiceUnitTest.java @@ -1,11 +1,11 @@ package at.tuwien.service; -import at.tuwien.api.container.internal.PrivilegedContainerDto; +import at.tuwien.api.container.ContainerDto; import at.tuwien.api.database.DatabaseAccessDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; -import at.tuwien.api.database.internal.PrivilegedViewDto; -import at.tuwien.api.database.table.internal.PrivilegedTableDto; -import at.tuwien.api.user.internal.PrivilegedUserDto; +import at.tuwien.api.database.DatabaseDto; +import at.tuwien.api.database.ViewDto; +import at.tuwien.api.database.table.TableDto; +import at.tuwien.api.user.UserDto; import at.tuwien.exception.*; import at.tuwien.gateway.MetadataServiceGateway; import at.tuwien.service.impl.CredentialServiceImpl; @@ -48,10 +48,10 @@ public class CredentialServiceUnitTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getDatabaseById(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); + .thenReturn(DATABASE_1_DTO); /* test */ - final PrivilegedDatabaseDto response = credentialService.getDatabase(DATABASE_1_ID); + final DatabaseDto response = credentialService.getDatabase(DATABASE_1_ID); assertNotNull(response); assertEquals(DATABASE_1_ID, response.getId()); } @@ -62,12 +62,12 @@ public class CredentialServiceUnitTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getDatabaseById(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO) + .thenReturn(DATABASE_1_DTO) .thenThrow(RuntimeException.class) /* should never be thrown */; credentialService.getDatabase(DATABASE_1_ID); /* test */ - final PrivilegedDatabaseDto response = credentialService.getDatabase(DATABASE_1_ID); + final DatabaseDto response = credentialService.getDatabase(DATABASE_1_ID); assertNotNull(response); assertEquals(DATABASE_1_ID, response.getId()); } @@ -78,16 +78,16 @@ public class CredentialServiceUnitTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getDatabaseById(DATABASE_1_ID)) - .thenReturn(DATABASE_2_PRIVILEGED_DTO) /* needs to be different id for test case */ - .thenReturn(DATABASE_1_PRIVILEGED_DTO); + .thenReturn(DATABASE_2_DTO) /* needs to be different id for test case */ + .thenReturn(DATABASE_1_DTO); /* pre-condition */ - final PrivilegedDatabaseDto tmp = credentialService.getDatabase(DATABASE_1_ID); + final DatabaseDto tmp = credentialService.getDatabase(DATABASE_1_ID); assertNotEquals(DATABASE_1_ID, tmp.getId()); Thread.sleep(5000); /* test */ - final PrivilegedDatabaseDto response = credentialService.getDatabase(DATABASE_1_ID); + final DatabaseDto response = credentialService.getDatabase(DATABASE_1_ID); assertNotNull(response); assertEquals(DATABASE_1_ID, response.getId()); } @@ -98,10 +98,10 @@ public class CredentialServiceUnitTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getContainerById(CONTAINER_1_ID)) - .thenReturn(CONTAINER_1_PRIVILEGED_DTO); + .thenReturn(CONTAINER_1_DTO); /* test */ - final PrivilegedContainerDto response = credentialService.getContainer(CONTAINER_1_ID); + final ContainerDto response = credentialService.getContainer(CONTAINER_1_ID); assertNotNull(response); assertEquals(CONTAINER_1_ID, response.getId()); } @@ -112,12 +112,12 @@ public class CredentialServiceUnitTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getContainerById(CONTAINER_1_ID)) - .thenReturn(CONTAINER_1_PRIVILEGED_DTO) + .thenReturn(CONTAINER_1_DTO) .thenThrow(RuntimeException.class) /* should never be thrown */; credentialService.getContainer(CONTAINER_1_ID); /* test */ - final PrivilegedContainerDto response = credentialService.getContainer(CONTAINER_1_ID); + final ContainerDto response = credentialService.getContainer(CONTAINER_1_ID); assertNotNull(response); assertEquals(CONTAINER_1_ID, response.getId()); } @@ -128,16 +128,16 @@ public class CredentialServiceUnitTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getContainerById(DATABASE_1_ID)) - .thenReturn(CONTAINER_2_PRIVILEGED_DTO) /* needs to be different id for test case */ - .thenReturn(CONTAINER_1_PRIVILEGED_DTO); + .thenReturn(CONTAINER_2_DTO) /* needs to be different id for test case */ + .thenReturn(CONTAINER_1_DTO); /* pre-condition */ - final PrivilegedContainerDto tmp = credentialService.getContainer(CONTAINER_1_ID); + final ContainerDto tmp = credentialService.getContainer(CONTAINER_1_ID); assertNotEquals(CONTAINER_1_ID, tmp.getId()); Thread.sleep(5000); /* test */ - final PrivilegedContainerDto response = credentialService.getContainer(CONTAINER_1_ID); + final ContainerDto response = credentialService.getContainer(CONTAINER_1_ID); assertNotNull(response); assertEquals(CONTAINER_1_ID, response.getId()); } @@ -147,11 +147,11 @@ public class CredentialServiceUnitTest extends AbstractUnitTest { UserNotFoundException { /* mock */ - when(metadataServiceGateway.getPrivilegedUserById(USER_1_ID)) - .thenReturn(USER_1_PRIVILEGED_DTO); + when(metadataServiceGateway.getUserById(USER_1_ID)) + .thenReturn(USER_1_DTO); /* test */ - final PrivilegedUserDto response = credentialService.getUser(USER_1_ID); + final UserDto response = credentialService.getUser(USER_1_ID); assertNotNull(response); assertEquals(USER_1_ID, response.getId()); } @@ -161,13 +161,13 @@ public class CredentialServiceUnitTest extends AbstractUnitTest { UserNotFoundException { /* mock */ - when(metadataServiceGateway.getPrivilegedUserById(USER_1_ID)) - .thenReturn(USER_1_PRIVILEGED_DTO) + when(metadataServiceGateway.getUserById(USER_1_ID)) + .thenReturn(USER_1_DTO) .thenThrow(RuntimeException.class) /* should never be thrown */; credentialService.getUser(USER_1_ID); /* test */ - final PrivilegedUserDto response = credentialService.getUser(USER_1_ID); + final UserDto response = credentialService.getUser(USER_1_ID); assertNotNull(response); assertEquals(USER_1_ID, response.getId()); } @@ -177,17 +177,17 @@ public class CredentialServiceUnitTest extends AbstractUnitTest { InterruptedException, UserNotFoundException { /* mock */ - when(metadataServiceGateway.getPrivilegedUserById(USER_1_ID)) - .thenReturn(USER_2_PRIVILEGED_DTO) /* needs to be different id for test case */ - .thenReturn(USER_1_PRIVILEGED_DTO); + when(metadataServiceGateway.getUserById(USER_1_ID)) + .thenReturn(USER_2_DTO) /* needs to be different id for test case */ + .thenReturn(USER_1_DTO); /* pre-condition */ - final PrivilegedUserDto tmp = credentialService.getUser(USER_1_ID); + final UserDto tmp = credentialService.getUser(USER_1_ID); assertNotEquals(USER_1_ID, tmp.getId()); Thread.sleep(5000); /* test */ - final PrivilegedUserDto response = credentialService.getUser(USER_1_ID); + final UserDto response = credentialService.getUser(USER_1_ID); assertNotNull(response); assertEquals(USER_1_ID, response.getId()); } @@ -251,10 +251,10 @@ public class CredentialServiceUnitTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getTableById(DATABASE_1_ID, TABLE_1_ID)) - .thenReturn(TABLE_1_PRIVILEGED_DTO); + .thenReturn(TABLE_1_DTO); /* test */ - final PrivilegedTableDto response = credentialService.getTable(DATABASE_1_ID, TABLE_1_ID); + final TableDto response = credentialService.getTable(DATABASE_1_ID, TABLE_1_ID); assertNotNull(response); assertEquals(TABLE_1_ID, response.getId()); } @@ -265,12 +265,12 @@ public class CredentialServiceUnitTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getTableById(DATABASE_1_ID, TABLE_1_ID)) - .thenReturn(TABLE_1_PRIVILEGED_DTO) + .thenReturn(TABLE_1_DTO) .thenThrow(RuntimeException.class) /* should never be thrown */; credentialService.getTable(DATABASE_1_ID, TABLE_1_ID); /* test */ - final PrivilegedTableDto response = credentialService.getTable(DATABASE_1_ID, TABLE_1_ID); + final TableDto response = credentialService.getTable(DATABASE_1_ID, TABLE_1_ID); assertNotNull(response); assertEquals(TABLE_1_ID, response.getId()); } @@ -281,16 +281,16 @@ public class CredentialServiceUnitTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getTableById(DATABASE_1_ID, TABLE_1_ID)) - .thenReturn(TABLE_2_PRIVILEGED_DTO) /* needs to be different id for test case */ - .thenReturn(TABLE_1_PRIVILEGED_DTO); + .thenReturn(TABLE_2_DTO) /* needs to be different id for test case */ + .thenReturn(TABLE_1_DTO); /* pre-condition */ - final PrivilegedTableDto tmp = credentialService.getTable(DATABASE_1_ID, TABLE_1_ID); + final TableDto tmp = credentialService.getTable(DATABASE_1_ID, TABLE_1_ID); assertNotEquals(TABLE_1_ID, tmp.getId()); Thread.sleep(5000); /* test */ - final PrivilegedTableDto response = credentialService.getTable(DATABASE_1_ID, TABLE_1_ID); + final TableDto response = credentialService.getTable(DATABASE_1_ID, TABLE_1_ID); assertNotNull(response); assertEquals(TABLE_1_ID, response.getId()); } @@ -301,10 +301,10 @@ public class CredentialServiceUnitTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getViewById(DATABASE_1_ID, VIEW_1_ID)) - .thenReturn(VIEW_1_PRIVILEGED_DTO); + .thenReturn(VIEW_1_DTO); /* test */ - final PrivilegedViewDto response = credentialService.getView(DATABASE_1_ID, VIEW_1_ID); + final ViewDto response = credentialService.getView(DATABASE_1_ID, VIEW_1_ID); assertNotNull(response); assertEquals(VIEW_1_ID, response.getId()); } @@ -314,12 +314,12 @@ public class CredentialServiceUnitTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getViewById(DATABASE_1_ID, VIEW_1_ID)) - .thenReturn(VIEW_1_PRIVILEGED_DTO) + .thenReturn(VIEW_1_DTO) .thenThrow(RuntimeException.class) /* should never be thrown */; credentialService.getView(DATABASE_1_ID, VIEW_1_ID); /* test */ - final PrivilegedViewDto response = credentialService.getView(DATABASE_1_ID, VIEW_1_ID); + final ViewDto response = credentialService.getView(DATABASE_1_ID, VIEW_1_ID); assertNotNull(response); assertEquals(VIEW_1_ID, response.getId()); } @@ -330,16 +330,16 @@ public class CredentialServiceUnitTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getViewById(DATABASE_1_ID, VIEW_1_ID)) - .thenReturn(VIEW_2_PRIVILEGED_DTO) /* needs to be different id for test case */ - .thenReturn(VIEW_1_PRIVILEGED_DTO); + .thenReturn(VIEW_2_DTO) /* needs to be different id for test case */ + .thenReturn(VIEW_1_DTO); /* pre-condition */ - final PrivilegedViewDto tmp = credentialService.getView(DATABASE_1_ID, VIEW_1_ID); + final ViewDto tmp = credentialService.getView(DATABASE_1_ID, VIEW_1_ID); assertNotEquals(VIEW_1_ID, tmp.getId()); Thread.sleep(5000); /* test */ - final PrivilegedViewDto response = credentialService.getView(DATABASE_1_ID, VIEW_1_ID); + final ViewDto response = credentialService.getView(DATABASE_1_ID, VIEW_1_ID); assertNotNull(response); assertEquals(VIEW_1_ID, response.getId()); } diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/DatabaseServiceIntegrationTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/DatabaseServiceIntegrationTest.java index 83d81715bd..26ff951dda 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/DatabaseServiceIntegrationTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/DatabaseServiceIntegrationTest.java @@ -1,12 +1,29 @@ package at.tuwien.service; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; +import at.tuwien.api.database.ViewColumnDto; +import at.tuwien.api.database.ViewDto; +import at.tuwien.api.database.table.TableBriefDto; +import at.tuwien.api.database.table.TableDto; +import at.tuwien.api.database.table.columns.ColumnCreateDto; +import at.tuwien.api.database.table.columns.ColumnDto; +import at.tuwien.api.database.table.columns.ColumnTypeDto; +import at.tuwien.api.database.table.constraints.ConstraintsCreateDto; +import at.tuwien.api.database.table.constraints.ConstraintsDto; +import at.tuwien.api.database.table.constraints.foreign.ForeignKeyCreateDto; +import at.tuwien.api.database.table.constraints.foreign.ForeignKeyDto; +import at.tuwien.api.database.table.constraints.foreign.ForeignKeyReferenceDto; +import at.tuwien.api.database.table.constraints.foreign.ReferenceTypeDto; +import at.tuwien.api.database.table.constraints.primary.PrimaryKeyDto; +import at.tuwien.api.database.table.constraints.unique.UniqueDto; +import at.tuwien.api.database.table.internal.TableCreateDto; +import at.tuwien.api.identifier.IdentifierDto; import at.tuwien.api.user.internal.UpdateUserPasswordDto; import at.tuwien.config.MariaDbConfig; import at.tuwien.config.MariaDbContainerConfig; -import at.tuwien.exception.DatabaseMalformedException; +import at.tuwien.exception.*; import at.tuwien.test.AbstractUnitTest; import lombok.extern.log4j.Log4j2; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -19,6 +36,9 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import java.sql.SQLException; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; import static org.junit.jupiter.api.Assertions.*; @@ -43,35 +63,39 @@ public class DatabaseServiceIntegrationTest extends AbstractUnitTest { public void beforeEach() throws SQLException { genesis(); /* metadata database */ - MariaDbConfig.dropDatabase(CONTAINER_1_PRIVILEGED_DTO, DATABASE_1_INTERNALNAME); + MariaDbConfig.dropDatabase(CONTAINER_1_DTO, DATABASE_1_INTERNALNAME); } @Test - public void create_succeeds() throws SQLException, DatabaseMalformedException { + public void createView_succeeds() throws SQLException, ViewMalformedException { /* test */ - final PrivilegedDatabaseDto response = databaseService.create(CONTAINER_1_PRIVILEGED_DTO, DATABASE_1_CREATE_INTERNAL); - assertNull(response.getName()); - assertEquals(DATABASE_1_INTERNALNAME, response.getInternalName()); - assertEquals(EXCHANGE_DBREPO_NAME, response.getExchangeName()); - assertNotNull(response.getOwner()); - assertEquals(USER_1_ID, response.getOwner().getId()); - assertNotNull(response.getContact()); - assertEquals(USER_1_ID, response.getContact().getId()); - assertNotNull(response.getContainer()); - assertEquals(CONTAINER_1_ID, response.getContainer().getId()); + databaseService.createView(DATABASE_1_DTO, VIEW_1_CREATE_DTO); } @Test - public void create_exists_fails() throws SQLException { - - /* mock */ - MariaDbConfig.createDatabase(CONTAINER_1_PRIVILEGED_DTO, DATABASE_1_INTERNALNAME); + public void exploreViews_succeeds() throws SQLException, ViewNotFoundException, DatabaseMalformedException { /* test */ - assertThrows(DatabaseMalformedException.class, () -> { - databaseService.create(CONTAINER_1_PRIVILEGED_DTO, DATABASE_1_CREATE_INTERNAL); - }); + final List<ViewDto> response = databaseService.exploreViews(DATABASE_1_DTO); + final ViewDto view0 = response.get(0); + assertEquals("not_in_metadata_db2", view0.getName()); + assertEquals("not_in_metadata_db2", view0.getInternalName()); + assertEquals(DATABASE_1_ID, view0.getVdbid()); + assertEquals(DATABASE_1_OWNER, view0.getOwner().getId()); + assertFalse(view0.getIsInitialView()); + assertEquals(DATABASE_1_PUBLIC, view0.getIsPublic()); + assertTrue(view0.getQuery().length() >= 69); + assertNotNull(view0.getQueryHash()); + assertEquals(4, view0.getColumns().size()); + final ViewColumnDto column0a = view0.getColumns().get(0); + assertEquals("date", column0a.getInternalName()); + final ViewColumnDto column1a = view0.getColumns().get(1); + assertEquals("location", column1a.getInternalName()); + final ViewColumnDto column2a = view0.getColumns().get(2); + assertEquals("MinTemp", column2a.getInternalName()); + final ViewColumnDto column3a = view0.getColumns().get(3); + assertEquals("Rainfall", column3a.getInternalName()); } @Test @@ -82,8 +106,8 @@ public class DatabaseServiceIntegrationTest extends AbstractUnitTest { .build(); /* mock */ - MariaDbConfig.createInitDatabase(CONTAINER_1_PRIVILEGED_DTO, DATABASE_1_DTO); - MariaDbConfig.grantWriteAccess(DATABASE_1_PRIVILEGED_DTO, USER_1_USERNAME); + MariaDbConfig.createInitDatabase(CONTAINER_1_DTO, DATABASE_1_DTO); + MariaDbConfig.grantWriteAccess(DATABASE_1_DTO, USER_1_USERNAME); /* pre-condition */ MariaDbConfig.mockQuery(CONTAINER_1_HOST, CONTAINER_1_PORT, DATABASE_1_INTERNALNAME, "CREATE SEQUENCE debug NOCACHE", USER_1_USERNAME, USER_1_PASSWORD); @@ -95,7 +119,7 @@ public class DatabaseServiceIntegrationTest extends AbstractUnitTest { } /* test */ - databaseService.update(DATABASE_1_PRIVILEGED_DTO, request); + databaseService.update(DATABASE_1_DTO, request); MariaDbConfig.mockQuery(CONTAINER_1_HOST, CONTAINER_1_PORT, DATABASE_1_INTERNALNAME, "CREATE SEQUENCE debug2 NOCACHE", USER_1_USERNAME, USER_2_PASSWORD); } @@ -107,12 +131,612 @@ public class DatabaseServiceIntegrationTest extends AbstractUnitTest { .build(); /* mock */ - MariaDbConfig.createInitDatabase(CONTAINER_1_PRIVILEGED_DTO, DATABASE_1_DTO); + MariaDbConfig.createInitDatabase(CONTAINER_1_DTO, DATABASE_1_DTO); /* test */ assertThrows(DatabaseMalformedException.class, () -> { - databaseService.update(DATABASE_1_PRIVILEGED_DTO, request); + databaseService.update(DATABASE_1_DTO, request); }); } + + @Test + public void inspectTable_sameNameDifferentDb_succeeds() throws TableNotFoundException, SQLException { + + /* mock */ + MariaDbConfig.execute(DATABASE_2_DTO, "CREATE TABLE not_in_metadata_db (wrong_id BIGINT NOT NULL PRIMARY KEY, given_name VARCHAR(255) NOT NULL, middle_name VARCHAR(255), family_name VARCHAR(255) NOT NULL, age INT NOT NULL) WITH SYSTEM VERSIONING;"); + + /* test */ + final TableDto response = databaseService.inspectTable(DATABASE_1_DTO, "not_in_metadata_db"); + assertEquals("not_in_metadata_db", response.getInternalName()); + assertEquals("not_in_metadata_db", response.getName()); + assertEquals(DATABASE_1_ID, response.getTdbid()); + assertTrue(response.getIsVersioned()); + assertEquals(DATABASE_1_PUBLIC, response.getIsPublic()); + final List<ColumnDto> columns = response.getColumns(); + assertNotNull(columns); + assertEquals(5, columns.size()); + assertColumn(columns.get(0), null, null, DATABASE_1_ID, "id", "id", ColumnTypeDto.BIGINT, 19L, 0L, false, null); + assertColumn(columns.get(1), null, null, DATABASE_1_ID, "given_name", "given_name", ColumnTypeDto.VARCHAR, 255L, null, false, null); + assertColumn(columns.get(2), null, null, DATABASE_1_ID, "middle_name", "middle_name", ColumnTypeDto.VARCHAR, 255L, null, true, null); + assertColumn(columns.get(3), null, null, DATABASE_1_ID, "family_name", "family_name", ColumnTypeDto.VARCHAR, 255L, null, false, null); + assertColumn(columns.get(4), null, null, DATABASE_1_ID, "age", "age", ColumnTypeDto.INT, 10L, 0L, false, null); + final ConstraintsDto constraints = response.getConstraints(); + assertNotNull(constraints); + final Set<PrimaryKeyDto> primaryKey = constraints.getPrimaryKey(); + assertEquals(1, primaryKey.size()); + final Set<String> checks = constraints.getChecks(); + assertEquals(1, checks.size()); + assertEquals(Set.of("`age` > 0 and `age` < 120"), checks); + final List<UniqueDto> uniques = constraints.getUniques(); + assertEquals(1, uniques.size()); + assertEquals(2, uniques.get(0).getColumns().size()); + assertEquals("not_in_metadata_db", uniques.get(0).getTable().getName()); + assertEquals("not_in_metadata_db", uniques.get(0).getTable().getInternalName()); + assertEquals("given_name", uniques.get(0).getColumns().get(0).getInternalName()); + assertEquals("family_name", uniques.get(0).getColumns().get(1).getInternalName()); + final List<ForeignKeyDto> foreignKeys = constraints.getForeignKeys(); + assertEquals(0, foreignKeys.size()); + } + + @Test + public void inspectTableEnum_succeeds() throws TableNotFoundException, SQLException { + + /* test */ + final TableDto response = databaseService.inspectTable(DATABASE_2_DTO, "experiments"); + assertEquals("experiments", response.getInternalName()); + assertEquals("experiments", response.getName()); + assertEquals(DATABASE_2_ID, response.getTdbid()); + assertTrue(response.getIsVersioned()); + assertEquals(DATABASE_2_PUBLIC, response.getIsPublic()); + assertNotNull(response.getOwner()); + assertEquals(DATABASE_2_OWNER, response.getOwner().getId()); + assertEquals(USER_2_NAME, response.getOwner().getName()); + assertEquals(USER_2_USERNAME, response.getOwner().getUsername()); + assertEquals(USER_2_FIRSTNAME, response.getOwner().getFirstname()); + assertEquals(USER_2_LASTNAME, response.getOwner().getLastname()); + assertEquals(USER_2_QUALIFIED_NAME, response.getOwner().getQualifiedName()); + final List<IdentifierDto> identifiers = response.getIdentifiers(); + assertNotNull(identifiers); + assertEquals(0, identifiers.size()); + final List<ColumnDto> columns = response.getColumns(); + assertNotNull(columns); + assertEquals(3, columns.size()); + assertColumn(columns.get(0), null, null, DATABASE_2_ID, "id", "id", ColumnTypeDto.BIGINT, 19L, 0L, false, null); + assertColumn(columns.get(1), null, null, DATABASE_2_ID, "mode", "mode", ColumnTypeDto.ENUM, 3L, null, false, null); + assertEquals(2, columns.get(1).getEnums().size()); + assertEquals(List.of("ABC", "DEF"), columns.get(1).getEnums()); + assertColumn(columns.get(2), null, null, DATABASE_2_ID, "seq", "seq", ColumnTypeDto.SET, 5L, null, true, null); + assertEquals(3, columns.get(2).getSets().size()); + assertEquals(List.of("1", "2", "3"), columns.get(2).getSets()); + /* ignore rest (constraints) */ + } + + @Test + public void inspectTableFullConstraints_succeeds() throws TableNotFoundException, SQLException { + + /* test */ + final TableDto response = databaseService.inspectTable(DATABASE_1_DTO, "weather_aus"); + assertEquals("weather_aus", response.getInternalName()); + assertEquals("weather_aus", response.getName()); + assertEquals(DATABASE_1_ID, response.getTdbid()); + assertTrue(response.getIsVersioned()); + assertEquals(DATABASE_1_PUBLIC, response.getIsPublic()); + assertNotNull(response.getOwner()); + assertEquals(DATABASE_1_OWNER, response.getOwner().getId()); + assertEquals(USER_1_NAME, response.getOwner().getName()); + assertEquals(USER_1_USERNAME, response.getOwner().getUsername()); + assertEquals(USER_1_FIRSTNAME, response.getOwner().getFirstname()); + assertEquals(USER_1_LASTNAME, response.getOwner().getLastname()); + assertEquals(USER_1_QUALIFIED_NAME, response.getOwner().getQualifiedName()); + final List<IdentifierDto> identifiers = response.getIdentifiers(); + assertNotNull(identifiers); + assertEquals(0, identifiers.size()); + final List<ColumnDto> columns = response.getColumns(); + assertNotNull(columns); + assertEquals(5, columns.size()); + assertColumn(columns.get(0), null, null, DATABASE_1_ID, "id", "id", ColumnTypeDto.BIGINT, 20L, 0L, false, null); + assertColumn(columns.get(1), null, null, DATABASE_1_ID, "date", "date", ColumnTypeDto.DATE, null, null, false, null); + assertColumn(columns.get(2), null, null, DATABASE_1_ID, "location", "location", ColumnTypeDto.VARCHAR, 255L, null, true, "Closest city"); + assertColumn(columns.get(3), null, null, DATABASE_1_ID, "mintemp", "mintemp", ColumnTypeDto.DOUBLE, 22L, null, true, null); + assertColumn(columns.get(4), null, null, DATABASE_1_ID, "rainfall", "rainfall", ColumnTypeDto.DOUBLE, 22L, null, true, null); + final ConstraintsDto constraints = response.getConstraints(); + final List<PrimaryKeyDto> primaryKey = new LinkedList<>(constraints.getPrimaryKey()); + assertEquals(1, primaryKey.size()); + final PrimaryKeyDto pk0 = primaryKey.get(0); + assertNull(pk0.getId()); + assertNotNull(pk0.getTable()); + assertNull(pk0.getTable().getId()); + assertEquals("weather_aus", pk0.getTable().getName()); + assertEquals("weather_aus", pk0.getTable().getInternalName()); + assertEquals("Weather in Australia", pk0.getTable().getDescription()); + assertNotNull(pk0.getColumn()); + assertNull(pk0.getColumn().getId()); + assertNull(pk0.getColumn().getTableId()); + assertEquals(DATABASE_1_ID, pk0.getColumn().getDatabaseId()); + assertNull(pk0.getColumn().getAlias()); + assertEquals("id", pk0.getColumn().getName()); + assertEquals("id", pk0.getColumn().getInternalName()); + assertEquals(ColumnTypeDto.BIGINT, pk0.getColumn().getColumnType()); + final List<UniqueDto> uniques = constraints.getUniques(); + assertEquals(1, uniques.size()); + final UniqueDto unique0 = uniques.get(0); + assertNotNull(unique0.getTable()); + assertEquals("some_constraint", unique0.getName()); + assertNull(unique0.getTable().getId()); + assertEquals(TABLE_1_INTERNAL_NAME, unique0.getTable().getName()); + assertEquals(TABLE_1_INTERNAL_NAME, unique0.getTable().getInternalName()); + assertEquals(TABLE_1_DESCRIPTION, unique0.getTable().getDescription()); + assertTrue(unique0.getTable().getIsVersioned()); + assertNotNull(unique0.getColumns()); + assertEquals(1, unique0.getColumns().size()); + assertNull(unique0.getColumns().get(0).getId()); + assertNull(unique0.getColumns().get(0).getTableId()); + assertEquals("date", unique0.getColumns().get(0).getName()); + assertEquals("date", unique0.getColumns().get(0).getInternalName()); + final List<String> checks = new LinkedList<>(constraints.getChecks()); + assertEquals("`mintemp` > 0", checks.get(0)); + final List<ForeignKeyDto> foreignKeys = constraints.getForeignKeys(); + assertEquals(1, foreignKeys.size()); + final ForeignKeyDto fk0 = foreignKeys.get(0); + assertNotNull(fk0.getName()); + assertNotNull(fk0.getReferences()); + final ForeignKeyReferenceDto fk0ref0 = fk0.getReferences().get(0); + assertNull(fk0ref0.getId()); + assertNotNull(fk0ref0.getColumn()); + assertNotNull(fk0ref0.getReferencedColumn()); + assertNotNull(fk0ref0.getForeignKey()); + assertEquals(DATABASE_1_ID, fk0ref0.getColumn().getDatabaseId()); + assertNull(fk0ref0.getColumn().getId()); + assertNull(fk0ref0.getColumn().getTableId()); + assertEquals("location", fk0ref0.getColumn().getName()); + assertEquals("location", fk0ref0.getColumn().getInternalName()); + assertEquals(DATABASE_1_ID, fk0ref0.getReferencedColumn().getDatabaseId()); + assertNull(fk0ref0.getReferencedColumn().getId()); + assertNull(fk0ref0.getReferencedColumn().getTableId()); + assertEquals("location", fk0ref0.getReferencedColumn().getName()); + assertEquals("location", fk0ref0.getReferencedColumn().getInternalName()); + assertNotNull(fk0.getOnUpdate()); + assertEquals(ReferenceTypeDto.RESTRICT, fk0.getOnUpdate()); + assertNotNull(fk0.getOnDelete()); + assertEquals(ReferenceTypeDto.SET_NULL, fk0.getOnDelete()); + final TableBriefDto fk0table = fk0.getTable(); + assertNull(fk0table.getId()); + assertEquals(DATABASE_1_ID, fk0table.getDatabaseId()); + assertEquals(TABLE_1_INTERNAL_NAME, fk0table.getName()); + assertEquals(TABLE_1_INTERNAL_NAME, fk0table.getInternalName()); + assertNotNull(fk0.getOnDelete()); + assertNotNull(fk0.getOnUpdate()); + assertNotNull(fk0.getReferencedTable()); + assertEquals(TABLE_2_INTERNALNAME, fk0.getReferencedTable().getName()); + assertEquals(TABLE_2_INTERNALNAME, fk0.getReferencedTable().getInternalName()); + } + + @Test + public void inspectTable_multipleForeignKeyReferences_succeeds() throws TableNotFoundException, SQLException { + + /* test */ + final TableDto response = databaseService.inspectTable(DATABASE_1_DTO, "complex_foreign_keys"); + final ConstraintsDto constraints = response.getConstraints(); + final List<ForeignKeyDto> foreignKeys = constraints.getForeignKeys(); + assertEquals(1, foreignKeys.size()); + final ForeignKeyDto fk0 = foreignKeys.get(0); + assertNotNull(fk0.getName()); + assertNotNull(fk0.getReferences()); + final ForeignKeyReferenceDto fk0ref0 = fk0.getReferences().get(0); + assertNull(fk0ref0.getId()); + assertNotNull(fk0ref0.getColumn()); + assertNotNull(fk0ref0.getReferencedColumn()); + assertNotNull(fk0ref0.getForeignKey()); + assertEquals(DATABASE_1_ID, fk0ref0.getColumn().getDatabaseId()); + assertNull(fk0ref0.getColumn().getId()); + assertNull(fk0ref0.getColumn().getTableId()); + assertEquals("weather_id", fk0ref0.getColumn().getName()); + assertEquals("weather_id", fk0ref0.getColumn().getInternalName()); + assertEquals(DATABASE_1_ID, fk0ref0.getReferencedColumn().getDatabaseId()); + assertNull(fk0ref0.getReferencedColumn().getId()); + assertNull(fk0ref0.getReferencedColumn().getTableId()); + assertEquals("id", fk0ref0.getReferencedColumn().getName()); + assertEquals("id", fk0ref0.getReferencedColumn().getInternalName()); + final ForeignKeyReferenceDto fk0ref1 = fk0.getReferences().get(1); + assertNull(fk0ref1.getId()); + assertNotNull(fk0ref1.getColumn()); + assertNotNull(fk0ref1.getReferencedColumn()); + assertNotNull(fk0ref1.getForeignKey()); + assertEquals(DATABASE_1_ID, fk0ref1.getColumn().getDatabaseId()); + assertNull(fk0ref1.getColumn().getId()); + assertNull(fk0ref1.getColumn().getTableId()); + assertEquals("other_id", fk0ref1.getColumn().getName()); + assertEquals("other_id", fk0ref1.getColumn().getInternalName()); + assertEquals(DATABASE_1_ID, fk0ref1.getReferencedColumn().getDatabaseId()); + assertNull(fk0ref1.getReferencedColumn().getId()); + assertNull(fk0ref1.getReferencedColumn().getTableId()); + assertEquals("other_id", fk0ref1.getReferencedColumn().getName()); + assertEquals("other_id", fk0ref1.getReferencedColumn().getInternalName()); + final TableBriefDto fk0refT0 = fk0.getTable(); + assertNull(fk0refT0.getId()); + assertEquals(DATABASE_1_ID, fk0refT0.getDatabaseId()); + assertEquals("complex_foreign_keys", fk0refT0.getName()); + assertEquals("complex_foreign_keys", fk0refT0.getInternalName()); + assertNotNull(fk0.getReferencedTable()); + assertEquals("complex_primary_key", fk0.getReferencedTable().getName()); + assertEquals("complex_primary_key", fk0.getReferencedTable().getInternalName()); + assertNotNull(fk0.getOnDelete()); + assertNotNull(fk0.getOnUpdate()); + } + + @Test + public void inspectTable_multiplePrimaryKey_succeeds() throws TableNotFoundException, SQLException { + + /* test */ + final TableDto response = databaseService.inspectTable(DATABASE_1_DTO, "complex_primary_key"); + final ConstraintsDto constraints = response.getConstraints(); + final List<PrimaryKeyDto> primaryKey = new LinkedList<>(constraints.getPrimaryKey()); + assertEquals(2, primaryKey.size()); + final PrimaryKeyDto pk0 = primaryKey.get(0); + assertNull(pk0.getId()); + assertNotNull(pk0.getTable()); + assertNull(pk0.getTable().getId()); + assertEquals("complex_primary_key", pk0.getTable().getName()); + assertEquals("complex_primary_key", pk0.getTable().getInternalName()); + assertNotNull(pk0.getColumn()); + assertNull(pk0.getColumn().getId()); + assertNull(pk0.getColumn().getTableId()); + assertEquals(DATABASE_1_ID, pk0.getColumn().getDatabaseId()); + assertNull(pk0.getColumn().getAlias()); + assertEquals("id", pk0.getColumn().getName()); + assertEquals("id", pk0.getColumn().getInternalName()); + assertEquals(ColumnTypeDto.BIGINT, pk0.getColumn().getColumnType()); + final PrimaryKeyDto pk1 = primaryKey.get(1); + assertNull(pk1.getId()); + assertNotNull(pk1.getTable()); + assertNull(pk1.getTable().getId()); + assertEquals("complex_primary_key", pk1.getTable().getName()); + assertEquals("complex_primary_key", pk1.getTable().getInternalName()); + assertNotNull(pk1.getColumn()); + assertNull(pk1.getColumn().getId()); + assertNull(pk1.getColumn().getTableId()); + assertEquals(DATABASE_1_ID, pk1.getColumn().getDatabaseId()); + assertNull(pk1.getColumn().getAlias()); + assertEquals("other_id", pk1.getColumn().getName()); + assertEquals("other_id", pk1.getColumn().getInternalName()); + assertEquals(ColumnTypeDto.BIGINT, pk1.getColumn().getColumnType()); + } + + @Test + public void inspectTable_exoticBoolean_succeeds() throws TableNotFoundException, SQLException { + + /* test */ + final TableDto response = databaseService.inspectTable(DATABASE_1_DTO, "exotic_boolean"); + final ConstraintsDto constraints = response.getConstraints(); + final List<PrimaryKeyDto> primaryKey = new LinkedList<>(constraints.getPrimaryKey()); + assertEquals(1, primaryKey.size()); + final PrimaryKeyDto pk0 = primaryKey.get(0); + assertNull(pk0.getId()); + assertNotNull(pk0.getTable()); + assertNull(pk0.getTable().getId()); + assertEquals("exotic_boolean", pk0.getTable().getName()); + assertEquals("exotic_boolean", pk0.getTable().getInternalName()); + assertNotNull(pk0.getColumn()); + assertNull(pk0.getColumn().getId()); + assertNull(pk0.getColumn().getTableId()); + assertEquals(DATABASE_1_ID, pk0.getColumn().getDatabaseId()); + assertNull(pk0.getColumn().getAlias()); + assertEquals("bool_default", pk0.getColumn().getName()); + assertEquals("bool_default", pk0.getColumn().getInternalName()); + assertEquals(ColumnTypeDto.BOOL, pk0.getColumn().getColumnType()); + final List<ColumnDto> columns = response.getColumns(); + assertEquals(3, columns.size()); + assertColumn(columns.get(0), null, null, DATABASE_1_ID, "bool_default", "bool_default", ColumnTypeDto.BOOL, null, 0L, false, null); + assertColumn(columns.get(1), null, null, DATABASE_1_ID, "bool_tinyint", "bool_tinyint", ColumnTypeDto.BOOL, null, 0L, false, null); + assertColumn(columns.get(2), null, null, DATABASE_1_ID, "bool_tinyint_unsigned", "bool_tinyint_unsigned", ColumnTypeDto.BOOL, null, 0L, false, null); + } + + @Test + public void inspectView_succeeds() throws SQLException, ViewNotFoundException { + + /* test */ + final ViewDto response = databaseService.inspectView(DATABASE_1_DTO, "not_in_metadata_db2"); + assertEquals("not_in_metadata_db2", response.getInternalName()); + assertEquals("not_in_metadata_db2", response.getName()); + assertEquals(DATABASE_1_ID, response.getVdbid()); + assertEquals(DATABASE_1_OWNER, response.getOwner().getId()); + assertFalse(response.getIsInitialView()); + assertEquals(DATABASE_1_PUBLIC, response.getIsPublic()); + assertTrue(response.getQuery().length() >= 69); + assertNotNull(response.getQueryHash()); + assertEquals(4, response.getColumns().size()); + final ViewColumnDto column0 = response.getColumns().get(0); + assertNotNull(column0.getName()); + assertEquals("date", column0.getInternalName()); + assertEquals(DATABASE_1_ID, column0.getDatabaseId()); + final ViewColumnDto column1 = response.getColumns().get(1); + assertNotNull(column1.getName()); + assertEquals("location", column1.getInternalName()); + assertEquals(DATABASE_1_ID, column1.getDatabaseId()); + final ViewColumnDto column2 = response.getColumns().get(2); + assertNotNull(column2.getName()); + assertEquals("MinTemp", column2.getInternalName()); + assertEquals(DATABASE_1_ID, column2.getDatabaseId()); + final ViewColumnDto column3 = response.getColumns().get(3); + assertNotNull(column3.getName()); + assertEquals("Rainfall", column3.getInternalName()); + assertEquals(DATABASE_1_ID, column3.getDatabaseId()); + } + + @Test + public void getSchemas_succeeds() throws TableNotFoundException, SQLException, DatabaseMalformedException { + + /* test */ + final List<TableDto> response = databaseService.exploreTables(DATABASE_1_DTO); + assertEquals(4, response.size()); + final TableDto table0 = response.get(0); + Assertions.assertEquals("complex_foreign_keys", table0.getInternalName()); + Assertions.assertEquals("complex_foreign_keys", table0.getName()); + Assertions.assertEquals(DATABASE_1_ID, table0.getTdbid()); + assertTrue(table0.getIsVersioned()); + Assertions.assertEquals(DATABASE_1_PUBLIC, table0.getIsPublic()); + final List<ColumnDto> columns0 = table0.getColumns(); + assertNotNull(columns0); + Assertions.assertEquals(3, columns0.size()); + assertColumn(columns0.get(0), null, null, DATABASE_1_ID, "id", "id", ColumnTypeDto.BIGINT, 19L, 0L, false, null); + assertColumn(columns0.get(1), null, null, DATABASE_1_ID, "weather_id", "weather_id", ColumnTypeDto.BIGINT, 19L, 0L, false, null); + assertColumn(columns0.get(2), null, null, DATABASE_1_ID, "other_id", "other_id", ColumnTypeDto.BIGINT, 19L, 0L, false, null); + final ConstraintsDto constraints0 = table0.getConstraints(); + assertNotNull(constraints0); + assertEquals(1, constraints0.getPrimaryKey().size()); + final PrimaryKeyDto pk0 = new LinkedList<>(constraints0.getPrimaryKey()).get(0); + assertNull(pk0.getId()); + assertNull(pk0.getColumn().getId()); + assertEquals("id", pk0.getColumn().getName()); + assertEquals("id", pk0.getColumn().getInternalName()); + assertEquals(1, constraints0.getForeignKeys().size()); + final ForeignKeyDto fk0 = constraints0.getForeignKeys().get(0); + assertNotNull(fk0.getName()); + assertNull(fk0.getTable().getId()); + assertEquals("complex_foreign_keys", fk0.getTable().getName()); + assertEquals("complex_foreign_keys", fk0.getTable().getInternalName()); + assertNull(fk0.getReferencedTable().getId()); + assertEquals("complex_primary_key", fk0.getReferencedTable().getName()); + assertEquals("complex_primary_key", fk0.getReferencedTable().getInternalName()); + assertEquals(2, fk0.getReferences().size()); + final ForeignKeyReferenceDto fk0r0 = fk0.getReferences().get(0); + assertEquals("weather_id", fk0r0.getColumn().getName()); + assertEquals("weather_id", fk0r0.getColumn().getInternalName()); + assertNotNull(fk0r0.getColumn().getName()); + assertNotNull(fk0r0.getForeignKey()); + assertEquals("id", fk0r0.getReferencedColumn().getName()); + assertEquals("id", fk0r0.getReferencedColumn().getInternalName()); + final ForeignKeyReferenceDto fk0r1 = fk0.getReferences().get(1); + assertEquals("other_id", fk0r1.getColumn().getName()); + assertEquals("other_id", fk0r1.getColumn().getInternalName()); + assertNotNull(fk0r1.getColumn().getName()); + assertNotNull(fk0r1.getForeignKey()); + assertEquals("other_id", fk0r1.getReferencedColumn().getName()); + assertEquals("other_id", fk0r1.getReferencedColumn().getInternalName()); + assertEquals(0, constraints0.getChecks().size()); + assertEquals(0, constraints0.getUniques().size()); + /* table 1 */ + final TableDto table1 = response.get(1); + Assertions.assertEquals("complex_primary_key", table1.getInternalName()); + Assertions.assertEquals("complex_primary_key", table1.getName()); + Assertions.assertEquals(DATABASE_1_ID, table1.getTdbid()); + assertTrue(table1.getIsVersioned()); + Assertions.assertEquals(DATABASE_1_PUBLIC, table1.getIsPublic()); + final List<ColumnDto> columns1 = table1.getColumns(); + assertNotNull(columns1); + Assertions.assertEquals(2, columns1.size()); + assertColumn(columns1.get(0), null, null, DATABASE_1_ID, "id", "id", ColumnTypeDto.BIGINT, 19L, 0L, false, null); + assertColumn(columns1.get(1), null, null, DATABASE_1_ID, "other_id", "other_id", ColumnTypeDto.BIGINT, 19L, 0L, false, null); + final ConstraintsDto constraints1 = table1.getConstraints(); + assertNotNull(constraints1); + assertEquals(2, constraints1.getPrimaryKey().size()); + final PrimaryKeyDto pk10 = new LinkedList<>(constraints1.getPrimaryKey()).get(0); + assertNull(pk10.getId()); + assertNull(pk10.getColumn().getId()); + assertEquals("id", pk10.getColumn().getName()); + assertEquals("id", pk10.getColumn().getInternalName()); + final PrimaryKeyDto pk11 = new LinkedList<>(constraints1.getPrimaryKey()).get(1); + assertNull(pk11.getId()); + assertNull(pk11.getColumn().getId()); + assertEquals("other_id", pk11.getColumn().getName()); + assertEquals("other_id", pk11.getColumn().getInternalName()); + assertEquals(0, constraints1.getForeignKeys().size()); + assertEquals(0, constraints1.getChecks().size()); + assertEquals(0, constraints1.getUniques().size()); + /* table 2 */ + final TableDto table2 = response.get(2); + Assertions.assertEquals("exotic_boolean", table2.getInternalName()); + Assertions.assertEquals("exotic_boolean", table2.getName()); + Assertions.assertEquals(DATABASE_1_ID, table2.getTdbid()); + assertTrue(table2.getIsVersioned()); + Assertions.assertEquals(DATABASE_1_PUBLIC, table2.getIsPublic()); + final List<ColumnDto> columns2 = table2.getColumns(); + assertNotNull(columns2); + Assertions.assertEquals(3, columns2.size()); + assertColumn(columns2.get(0), null, null, DATABASE_1_ID, "bool_default", "bool_default", ColumnTypeDto.BOOL, null, 0L, false, null); + assertColumn(columns2.get(1), null, null, DATABASE_1_ID, "bool_tinyint", "bool_tinyint", ColumnTypeDto.BOOL, null, 0L, false, null); + assertColumn(columns2.get(2), null, null, DATABASE_1_ID, "bool_tinyint_unsigned", "bool_tinyint_unsigned", ColumnTypeDto.BOOL, null, 0L, false, null); + final ConstraintsDto constraints2 = table2.getConstraints(); + assertNotNull(constraints2); + final Set<PrimaryKeyDto> primaryKey2 = constraints2.getPrimaryKey(); + Assertions.assertEquals(1, primaryKey2.size()); + final Set<String> checks2 = constraints2.getChecks(); + Assertions.assertEquals(0, checks2.size()); + final List<UniqueDto> uniques2 = constraints2.getUniques(); + Assertions.assertEquals(0, uniques2.size()); + /* table 3 */ + final TableDto table3 = response.get(3); + Assertions.assertEquals("not_in_metadata_db", table3.getInternalName()); + Assertions.assertEquals("not_in_metadata_db", table3.getName()); + Assertions.assertEquals(DATABASE_1_ID, table3.getTdbid()); + assertTrue(table3.getIsVersioned()); + Assertions.assertEquals(DATABASE_1_PUBLIC, table3.getIsPublic()); + final List<ColumnDto> columns3 = table3.getColumns(); + assertNotNull(columns3); + Assertions.assertEquals(5, columns3.size()); + assertColumn(columns3.get(0), null, null, DATABASE_1_ID, "id", "id", ColumnTypeDto.BIGINT, 19L, 0L, false, null); + assertColumn(columns3.get(1), null, null, DATABASE_1_ID, "given_name", "given_name", ColumnTypeDto.VARCHAR, 255L, null, false, null); + assertColumn(columns3.get(2), null, null, DATABASE_1_ID, "middle_name", "middle_name", ColumnTypeDto.VARCHAR, 255L, null, true, null); + assertColumn(columns3.get(3), null, null, DATABASE_1_ID, "family_name", "family_name", ColumnTypeDto.VARCHAR, 255L, null, false, null); + assertColumn(columns3.get(4), null, null, DATABASE_1_ID, "age", "age", ColumnTypeDto.INT, 10L, 0L, false, null); + final ConstraintsDto constraints3 = table3.getConstraints(); + assertNotNull(constraints3); + final Set<PrimaryKeyDto> primaryKey3 = constraints3.getPrimaryKey(); + Assertions.assertEquals(1, primaryKey3.size()); + final Set<String> checks3 = constraints3.getChecks(); + Assertions.assertEquals(1, checks3.size()); + Assertions.assertEquals(Set.of("`age` > 0 and `age` < 120"), checks3); + final List<UniqueDto> uniques3 = constraints3.getUniques(); + Assertions.assertEquals(1, uniques3.size()); + Assertions.assertEquals(2, uniques3.get(0).getColumns().size()); + Assertions.assertEquals("not_in_metadata_db", uniques3.get(0).getTable().getInternalName()); + Assertions.assertEquals("given_name", uniques3.get(0).getColumns().get(0).getInternalName()); + Assertions.assertEquals("family_name", uniques3.get(0).getColumns().get(1).getInternalName()); + } + + @Test + public void createTable_succeeds() throws TableNotFoundException, TableMalformedException, SQLException, + TableExistsException { + + /* test */ + final TableDto response = databaseService.createTable(DATABASE_1_DTO, TABLE_4_CREATE_INTERNAL_DTO); + assertEquals(TABLE_4_NAME, response.getName()); + assertEquals(TABLE_4_INTERNALNAME, response.getInternalName()); + final List<ColumnDto> columns = response.getColumns(); + assertEquals(TABLE_4_COLUMNS.size(), columns.size()); + assertColumn(columns.get(0), null, null, DATABASE_1_ID, "timestamp", "timestamp", ColumnTypeDto.TIMESTAMP, null, null, false, null); + assertColumn(columns.get(1), null, null, DATABASE_1_ID, "value", "value", ColumnTypeDto.DECIMAL, 10L, 10L, true, null); + final ConstraintsDto constraints = response.getConstraints(); + assertNotNull(constraints); + final Set<PrimaryKeyDto> primaryKey = constraints.getPrimaryKey(); + Assertions.assertEquals(1, primaryKey.size()); + final Set<String> checks = constraints.getChecks(); + Assertions.assertEquals(0, checks.size()); + } + + @Test + public void createTable_malformed_fails() { + final at.tuwien.api.database.table.internal.TableCreateDto request = TableCreateDto.builder() + .name("missing_foreign_key") + .columns(List.of()) + .constraints(ConstraintsCreateDto.builder() + .foreignKeys(List.of(ForeignKeyCreateDto.builder() + .columns(List.of("i_do_not_exist")) + .referencedTable("neither_do_i") + .referencedColumns(List.of("behold")) + .build())) + .build()) + .build(); + + /* test */ + assertThrows(TableMalformedException.class, () -> { + databaseService.createTable(DATABASE_1_DTO, request); + }); + } + + @Test + public void createTable_compositePrimaryKey_fails() throws TableNotFoundException, TableMalformedException, SQLException, + TableExistsException { + final at.tuwien.api.database.table.internal.TableCreateDto request = TableCreateDto.builder() + .name("composite_primary_key") + .columns(List.of(ColumnCreateDto.builder() + .name("name") + .type(ColumnTypeDto.VARCHAR) + .size(255L) + .nullAllowed(false) + .build(), + ColumnCreateDto.builder() + .name("lat") + .type(ColumnTypeDto.DECIMAL) + .size(10L) + .d(10L) + .nullAllowed(false) + .build(), + ColumnCreateDto.builder() + .name("lng") + .type(ColumnTypeDto.DECIMAL) + .size(10L) + .d(10L) + .nullAllowed(false) + .build())) + .constraints(ConstraintsCreateDto.builder() + .primaryKey(Set.of("lat", "lng")) + .foreignKeys(List.of()) + .checks(Set.of()) + .uniques(List.of()) + .build()) + .build(); + + /* test */ + final TableDto response = databaseService.createTable(DATABASE_1_DTO, request); + assertEquals("composite_primary_key", response.getName()); + assertEquals("composite_primary_key", response.getInternalName()); + final List<ColumnDto> columns = response.getColumns(); + assertEquals(3, columns.size()); + assertColumn(columns.get(0), null, null, DATABASE_1_ID, "name", "name", ColumnTypeDto.VARCHAR, 255L, null, false, null); + assertColumn(columns.get(1), null, null, DATABASE_1_ID, "lat", "lat", ColumnTypeDto.DECIMAL, 10L, 10L, false, null); + assertColumn(columns.get(2), null, null, DATABASE_1_ID, "lng", "lng", ColumnTypeDto.DECIMAL, 10L, 10L, false, null); + final ConstraintsDto constraints = response.getConstraints(); + assertNotNull(constraints); + final Set<String> checks = constraints.getChecks(); + assertNotNull(checks); + assertEquals(0, checks.size()); + final List<PrimaryKeyDto> primaryKeys = new LinkedList<>(constraints.getPrimaryKey()); + assertNotNull(primaryKeys); + assertEquals(2, primaryKeys.size()); + assertEquals("lat", primaryKeys.get(0).getColumn().getInternalName()); + assertEquals("lng", primaryKeys.get(1).getColumn().getInternalName()); + final List<ForeignKeyDto> foreignKeys = constraints.getForeignKeys(); + assertNotNull(foreignKeys); + assertEquals(0, foreignKeys.size()); + final List<UniqueDto> uniques = constraints.getUniques(); + assertNotNull(uniques); + assertEquals(0, uniques.size()); + } + + @Test + public void createTable_needSequence_succeeds() throws TableNotFoundException, TableMalformedException, SQLException, + TableExistsException { + + /* mock */ + MariaDbConfig.dropTable(DATABASE_1_DTO, TABLE_1_INTERNAL_NAME); + + /* test */ + final TableDto response = databaseService.createTable(DATABASE_1_DTO, TABLE_1_CREATE_INTERNAL_DTO); + assertEquals(TABLE_1_NAME, response.getName()); + assertEquals(TABLE_1_INTERNAL_NAME, response.getInternalName()); + assertEquals(TABLE_1_COLUMNS.size(), response.getColumns().size()); + } + + protected static void assertViewColumn(ViewColumnDto column, ViewColumnDto other) { + assertNotNull(column); + assertNotNull(other); + assertEquals(column.getId(), other.getId()); + assertEquals(column.getDatabaseId(), other.getDatabaseId()); + assertEquals(column.getName(), other.getName()); + assertEquals(column.getInternalName(), other.getInternalName()); + assertEquals(column.getColumnType(), other.getColumnType()); + assertEquals(column.getSize(), other.getSize()); + assertEquals(column.getD(), other.getD()); + assertEquals(column.getIsNullAllowed(), other.getIsNullAllowed()); + assertEquals(column.getDescription(), other.getDescription()); + } + + protected static void assertColumn(ColumnDto column, Long id, Long tableId, Long databaseId, String name, + String internalName, ColumnTypeDto type, Long size, Long d, Boolean nullAllowed, + String description) { + log.trace("assert column: {}", internalName); + assertNotNull(column); + assertEquals(id, column.getId()); + assertEquals(tableId, column.getTableId()); + assertEquals(databaseId, column.getDatabaseId()); + assertEquals(name, column.getName()); + assertEquals(internalName, column.getInternalName()); + assertEquals(type, column.getColumnType()); + assertEquals(size, column.getSize()); + assertEquals(d, column.getD()); + assertEquals(nullAllowed, column.getIsNullAllowed()); + assertEquals(description, column.getDescription()); + } } diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/QueueServiceIntegrationTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/QueueServiceIntegrationTest.java index bd57eafacc..a2ff1716e5 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/QueueServiceIntegrationTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/QueueServiceIntegrationTest.java @@ -52,8 +52,8 @@ public class QueueServiceIntegrationTest extends AbstractUnitTest { public void beforeEach() throws SQLException { genesis(); /* metadata database */ - MariaDbConfig.dropDatabase(CONTAINER_1_PRIVILEGED_DTO, DATABASE_1_INTERNALNAME); - MariaDbConfig.createInitDatabase(CONTAINER_1_PRIVILEGED_DTO, DATABASE_1_DTO); + MariaDbConfig.dropDatabase(CONTAINER_1_DTO, DATABASE_1_INTERNALNAME); + MariaDbConfig.createInitDatabase(CONTAINER_1_DTO, DATABASE_1_DTO); } @Test @@ -69,12 +69,12 @@ public class QueueServiceIntegrationTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getContainerById(CONTAINER_1_ID)) - .thenReturn(CONTAINER_1_PRIVILEGED_DTO); + .thenReturn(CONTAINER_1_DTO); when(metadataServiceGateway.getTableById(DATABASE_1_ID, TABLE_1_ID)) - .thenReturn(TABLE_1_PRIVILEGED_DTO); + .thenReturn(TABLE_1_DTO); /* test */ - queueService.insert(TABLE_1_PRIVILEGED_DTO, request); + queueService.insert(TABLE_1_DTO, request); } @Test @@ -87,10 +87,10 @@ public class QueueServiceIntegrationTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getTableById(DATABASE_1_ID, TABLE_1_ID)) - .thenReturn(TABLE_1_PRIVILEGED_DTO); + .thenReturn(TABLE_1_DTO); /* test */ - queueService.insert(TABLE_1_PRIVILEGED_DTO, request); + queueService.insert(TABLE_1_DTO, request); } } diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/SchemaServiceIntegrationTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/SchemaServiceIntegrationTest.java deleted file mode 100644 index 2a0ad624db..0000000000 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/SchemaServiceIntegrationTest.java +++ /dev/null @@ -1,416 +0,0 @@ -package at.tuwien.service; - -import at.tuwien.api.database.ViewColumnDto; -import at.tuwien.api.database.ViewDto; -import at.tuwien.api.database.table.TableBriefDto; -import at.tuwien.api.database.table.TableDto; -import at.tuwien.api.database.table.columns.ColumnDto; -import at.tuwien.api.database.table.columns.ColumnTypeDto; -import at.tuwien.api.database.table.constraints.ConstraintsDto; -import at.tuwien.api.database.table.constraints.foreign.ForeignKeyDto; -import at.tuwien.api.database.table.constraints.foreign.ForeignKeyReferenceDto; -import at.tuwien.api.database.table.constraints.foreign.ReferenceTypeDto; -import at.tuwien.api.database.table.constraints.primary.PrimaryKeyDto; -import at.tuwien.api.database.table.constraints.unique.UniqueDto; -import at.tuwien.api.identifier.IdentifierDto; -import at.tuwien.config.MariaDbConfig; -import at.tuwien.config.MariaDbContainerConfig; -import at.tuwien.exception.TableNotFoundException; -import at.tuwien.exception.ViewNotFoundException; -import at.tuwien.test.AbstractUnitTest; -import lombok.extern.log4j.Log4j2; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.testcontainers.containers.MariaDBContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; - -import java.sql.SQLException; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; - -import static org.junit.jupiter.api.Assertions.*; - -@Log4j2 -@SpringBootTest -@ExtendWith(SpringExtension.class) -@Testcontainers -public class SchemaServiceIntegrationTest extends AbstractUnitTest { - - @Autowired - private SchemaService schemaService; - - @Container - private static MariaDBContainer<?> mariaDBContainer = MariaDbContainerConfig.getContainer(); - - @BeforeEach - public void beforeEach() throws SQLException { - genesis(); - /* metadata database */ - MariaDbConfig.dropDatabase(CONTAINER_1_PRIVILEGED_DTO, DATABASE_1_INTERNALNAME); - MariaDbConfig.dropDatabase(CONTAINER_1_PRIVILEGED_DTO, DATABASE_2_INTERNALNAME); - MariaDbConfig.createInitDatabase(CONTAINER_1_PRIVILEGED_DTO, DATABASE_1_DTO); - MariaDbConfig.createInitDatabase(CONTAINER_1_PRIVILEGED_DTO, DATABASE_2_DTO); - } - - @Test - public void inspectTable_sameNameDifferentDb_succeeds() throws TableNotFoundException, SQLException { - - /* mock */ - MariaDbConfig.execute(DATABASE_2_PRIVILEGED_DTO, "CREATE TABLE not_in_metadata_db (wrong_id BIGINT NOT NULL PRIMARY KEY, given_name VARCHAR(255) NOT NULL, middle_name VARCHAR(255), family_name VARCHAR(255) NOT NULL, age INT NOT NULL) WITH SYSTEM VERSIONING;"); - - /* test */ - final TableDto response = schemaService.inspectTable(DATABASE_1_PRIVILEGED_DTO, "not_in_metadata_db"); - assertEquals("not_in_metadata_db", response.getInternalName()); - assertEquals("not_in_metadata_db", response.getName()); - assertEquals(DATABASE_1_ID, response.getTdbid()); - assertTrue(response.getIsVersioned()); - assertEquals(DATABASE_1_PUBLIC, response.getIsPublic()); - final List<ColumnDto> columns = response.getColumns(); - assertNotNull(columns); - assertEquals(5, columns.size()); - assertColumn(columns.get(0), null, null, DATABASE_1_ID, "id", "id", ColumnTypeDto.BIGINT, 19L, 0L, false, null); - assertColumn(columns.get(1), null, null, DATABASE_1_ID, "given_name", "given_name", ColumnTypeDto.VARCHAR, 255L, null, false, null); - assertColumn(columns.get(2), null, null, DATABASE_1_ID, "middle_name", "middle_name", ColumnTypeDto.VARCHAR, 255L, null, true, null); - assertColumn(columns.get(3), null, null, DATABASE_1_ID, "family_name", "family_name", ColumnTypeDto.VARCHAR, 255L, null, false, null); - assertColumn(columns.get(4), null, null, DATABASE_1_ID, "age", "age", ColumnTypeDto.INT, 10L, 0L, false, null); - final ConstraintsDto constraints = response.getConstraints(); - assertNotNull(constraints); - final Set<PrimaryKeyDto> primaryKey = constraints.getPrimaryKey(); - assertEquals(1, primaryKey.size()); - final Set<String> checks = constraints.getChecks(); - assertEquals(1, checks.size()); - assertEquals(Set.of("`age` > 0 and `age` < 120"), checks); - final List<UniqueDto> uniques = constraints.getUniques(); - assertEquals(1, uniques.size()); - assertEquals(2, uniques.get(0).getColumns().size()); - assertEquals("not_in_metadata_db", uniques.get(0).getTable().getName()); - assertEquals("not_in_metadata_db", uniques.get(0).getTable().getInternalName()); - assertEquals("given_name", uniques.get(0).getColumns().get(0).getInternalName()); - assertEquals("family_name", uniques.get(0).getColumns().get(1).getInternalName()); - final List<ForeignKeyDto> foreignKeys = constraints.getForeignKeys(); - assertEquals(0, foreignKeys.size()); - } - - @Test - public void inspectTableEnum_succeeds() throws TableNotFoundException, SQLException { - - /* test */ - final TableDto response = schemaService.inspectTable(DATABASE_2_PRIVILEGED_DTO, "experiments"); - assertEquals("experiments", response.getInternalName()); - assertEquals("experiments", response.getName()); - assertEquals(DATABASE_2_ID, response.getTdbid()); - assertTrue(response.getIsVersioned()); - assertEquals(DATABASE_2_PUBLIC, response.getIsPublic()); - assertNotNull(response.getOwner()); - assertEquals(DATABASE_2_OWNER, response.getOwner().getId()); - assertEquals(USER_2_NAME, response.getOwner().getName()); - assertEquals(USER_2_USERNAME, response.getOwner().getUsername()); - assertEquals(USER_2_FIRSTNAME, response.getOwner().getFirstname()); - assertEquals(USER_2_LASTNAME, response.getOwner().getLastname()); - assertEquals(USER_2_QUALIFIED_NAME, response.getOwner().getQualifiedName()); - final List<IdentifierDto> identifiers = response.getIdentifiers(); - assertNotNull(identifiers); - assertEquals(0, identifiers.size()); - final List<ColumnDto> columns = response.getColumns(); - assertNotNull(columns); - assertEquals(3, columns.size()); - assertColumn(columns.get(0), null, null, DATABASE_2_ID, "id", "id", ColumnTypeDto.BIGINT, 19L, 0L, false, null); - assertColumn(columns.get(1), null, null, DATABASE_2_ID, "mode", "mode", ColumnTypeDto.ENUM, 3L, null, false, null); - assertEquals(2, columns.get(1).getEnums().size()); - assertEquals(List.of("ABC", "DEF"), columns.get(1).getEnums()); - assertColumn(columns.get(2), null, null, DATABASE_2_ID, "seq", "seq", ColumnTypeDto.SET, 5L, null, true, null); - assertEquals(3, columns.get(2).getSets().size()); - assertEquals(List.of("1", "2", "3"), columns.get(2).getSets()); - /* ignore rest (constraints) */ - } - - @Test - public void inspectTableFullConstraints_succeeds() throws TableNotFoundException, SQLException { - - /* test */ - final TableDto response = schemaService.inspectTable(DATABASE_1_PRIVILEGED_DTO, "weather_aus"); - assertEquals("weather_aus", response.getInternalName()); - assertEquals("weather_aus", response.getName()); - assertEquals(DATABASE_1_ID, response.getTdbid()); - assertTrue(response.getIsVersioned()); - assertEquals(DATABASE_1_PUBLIC, response.getIsPublic()); - assertNotNull(response.getOwner()); - assertEquals(DATABASE_1_OWNER, response.getOwner().getId()); - assertEquals(USER_1_NAME, response.getOwner().getName()); - assertEquals(USER_1_USERNAME, response.getOwner().getUsername()); - assertEquals(USER_1_FIRSTNAME, response.getOwner().getFirstname()); - assertEquals(USER_1_LASTNAME, response.getOwner().getLastname()); - assertEquals(USER_1_QUALIFIED_NAME, response.getOwner().getQualifiedName()); - final List<IdentifierDto> identifiers = response.getIdentifiers(); - assertNotNull(identifiers); - assertEquals(0, identifiers.size()); - final List<ColumnDto> columns = response.getColumns(); - assertNotNull(columns); - assertEquals(5, columns.size()); - assertColumn(columns.get(0), null, null, DATABASE_1_ID, "id", "id", ColumnTypeDto.BIGINT, 20L, 0L, false, null); - assertColumn(columns.get(1), null, null, DATABASE_1_ID, "date", "date", ColumnTypeDto.DATE, null, null, false, null); - assertColumn(columns.get(2), null, null, DATABASE_1_ID, "location", "location", ColumnTypeDto.VARCHAR, 255L, null, true, "Closest city"); - assertColumn(columns.get(3), null, null, DATABASE_1_ID, "mintemp", "mintemp", ColumnTypeDto.DOUBLE, 22L, null, true, null); - assertColumn(columns.get(4), null, null, DATABASE_1_ID, "rainfall", "rainfall", ColumnTypeDto.DOUBLE, 22L, null, true, null); - final ConstraintsDto constraints = response.getConstraints(); - final List<PrimaryKeyDto> primaryKey = new LinkedList<>(constraints.getPrimaryKey()); - assertEquals(1, primaryKey.size()); - final PrimaryKeyDto pk0 = primaryKey.get(0); - assertNull(pk0.getId()); - assertNotNull(pk0.getTable()); - assertNull(pk0.getTable().getId()); - assertEquals("weather_aus", pk0.getTable().getName()); - assertEquals("weather_aus", pk0.getTable().getInternalName()); - assertEquals("Weather in Australia", pk0.getTable().getDescription()); - assertNotNull(pk0.getColumn()); - assertNull(pk0.getColumn().getId()); - assertNull(pk0.getColumn().getTableId()); - assertEquals(DATABASE_1_ID, pk0.getColumn().getDatabaseId()); - assertNull(pk0.getColumn().getAlias()); - assertEquals("id", pk0.getColumn().getName()); - assertEquals("id", pk0.getColumn().getInternalName()); - assertEquals(ColumnTypeDto.BIGINT, pk0.getColumn().getColumnType()); - final List<UniqueDto> uniques = constraints.getUniques(); - assertEquals(1, uniques.size()); - final UniqueDto unique0 = uniques.get(0); - assertNotNull(unique0.getTable()); - assertEquals("some_constraint", unique0.getName()); - assertNull(unique0.getTable().getId()); - assertEquals(TABLE_1_INTERNAL_NAME, unique0.getTable().getName()); - assertEquals(TABLE_1_INTERNAL_NAME, unique0.getTable().getInternalName()); - assertEquals(TABLE_1_DESCRIPTION, unique0.getTable().getDescription()); - assertTrue(unique0.getTable().getIsVersioned()); - assertNotNull(unique0.getColumns()); - assertEquals(1, unique0.getColumns().size()); - assertNull(unique0.getColumns().get(0).getId()); - assertNull(unique0.getColumns().get(0).getTableId()); - assertEquals("date", unique0.getColumns().get(0).getName()); - assertEquals("date", unique0.getColumns().get(0).getInternalName()); - final List<String> checks = new LinkedList<>(constraints.getChecks()); - assertEquals("`mintemp` > 0", checks.get(0)); - final List<ForeignKeyDto> foreignKeys = constraints.getForeignKeys(); - assertEquals(1, foreignKeys.size()); - final ForeignKeyDto fk0 = foreignKeys.get(0); - assertNotNull(fk0.getName()); - assertNotNull(fk0.getReferences()); - final ForeignKeyReferenceDto fk0ref0 = fk0.getReferences().get(0); - assertNull(fk0ref0.getId()); - assertNotNull(fk0ref0.getColumn()); - assertNotNull(fk0ref0.getReferencedColumn()); - assertNotNull(fk0ref0.getForeignKey()); - assertEquals(DATABASE_1_ID, fk0ref0.getColumn().getDatabaseId()); - assertNull(fk0ref0.getColumn().getId()); - assertNull(fk0ref0.getColumn().getTableId()); - assertEquals("location", fk0ref0.getColumn().getName()); - assertEquals("location", fk0ref0.getColumn().getInternalName()); - assertEquals(DATABASE_1_ID, fk0ref0.getReferencedColumn().getDatabaseId()); - assertNull(fk0ref0.getReferencedColumn().getId()); - assertNull(fk0ref0.getReferencedColumn().getTableId()); - assertEquals("location", fk0ref0.getReferencedColumn().getName()); - assertEquals("location", fk0ref0.getReferencedColumn().getInternalName()); - assertNotNull(fk0.getOnUpdate()); - assertEquals(ReferenceTypeDto.RESTRICT, fk0.getOnUpdate()); - assertNotNull(fk0.getOnDelete()); - assertEquals(ReferenceTypeDto.SET_NULL, fk0.getOnDelete()); - final TableBriefDto fk0table = fk0.getTable(); - assertNull(fk0table.getId()); - assertEquals(DATABASE_1_ID, fk0table.getDatabaseId()); - assertEquals(TABLE_1_INTERNAL_NAME, fk0table.getName()); - assertEquals(TABLE_1_INTERNAL_NAME, fk0table.getInternalName()); - assertNotNull(fk0.getOnDelete()); - assertNotNull(fk0.getOnUpdate()); - assertNotNull(fk0.getReferencedTable()); - assertEquals(TABLE_2_INTERNALNAME, fk0.getReferencedTable().getName()); - assertEquals(TABLE_2_INTERNALNAME, fk0.getReferencedTable().getInternalName()); - } - - @Test - public void inspectTable_multipleForeignKeyReferences_succeeds() throws TableNotFoundException, SQLException { - - /* test */ - final TableDto response = schemaService.inspectTable(DATABASE_1_PRIVILEGED_DTO, "complex_foreign_keys"); - final ConstraintsDto constraints = response.getConstraints(); - final List<ForeignKeyDto> foreignKeys = constraints.getForeignKeys(); - assertEquals(1, foreignKeys.size()); - final ForeignKeyDto fk0 = foreignKeys.get(0); - assertNotNull(fk0.getName()); - assertNotNull(fk0.getReferences()); - final ForeignKeyReferenceDto fk0ref0 = fk0.getReferences().get(0); - assertNull(fk0ref0.getId()); - assertNotNull(fk0ref0.getColumn()); - assertNotNull(fk0ref0.getReferencedColumn()); - assertNotNull(fk0ref0.getForeignKey()); - assertEquals(DATABASE_1_ID, fk0ref0.getColumn().getDatabaseId()); - assertNull(fk0ref0.getColumn().getId()); - assertNull(fk0ref0.getColumn().getTableId()); - assertEquals("weather_id", fk0ref0.getColumn().getName()); - assertEquals("weather_id", fk0ref0.getColumn().getInternalName()); - assertEquals(DATABASE_1_ID, fk0ref0.getReferencedColumn().getDatabaseId()); - assertNull(fk0ref0.getReferencedColumn().getId()); - assertNull(fk0ref0.getReferencedColumn().getTableId()); - assertEquals("id", fk0ref0.getReferencedColumn().getName()); - assertEquals("id", fk0ref0.getReferencedColumn().getInternalName()); - final ForeignKeyReferenceDto fk0ref1 = fk0.getReferences().get(1); - assertNull(fk0ref1.getId()); - assertNotNull(fk0ref1.getColumn()); - assertNotNull(fk0ref1.getReferencedColumn()); - assertNotNull(fk0ref1.getForeignKey()); - assertEquals(DATABASE_1_ID, fk0ref1.getColumn().getDatabaseId()); - assertNull(fk0ref1.getColumn().getId()); - assertNull(fk0ref1.getColumn().getTableId()); - assertEquals("other_id", fk0ref1.getColumn().getName()); - assertEquals("other_id", fk0ref1.getColumn().getInternalName()); - assertEquals(DATABASE_1_ID, fk0ref1.getReferencedColumn().getDatabaseId()); - assertNull(fk0ref1.getReferencedColumn().getId()); - assertNull(fk0ref1.getReferencedColumn().getTableId()); - assertEquals("other_id", fk0ref1.getReferencedColumn().getName()); - assertEquals("other_id", fk0ref1.getReferencedColumn().getInternalName()); - final TableBriefDto fk0refT0 = fk0.getTable(); - assertNull(fk0refT0.getId()); - assertEquals(DATABASE_1_ID, fk0refT0.getDatabaseId()); - assertEquals("complex_foreign_keys", fk0refT0.getName()); - assertEquals("complex_foreign_keys", fk0refT0.getInternalName()); - assertNotNull(fk0.getReferencedTable()); - assertEquals("complex_primary_key", fk0.getReferencedTable().getName()); - assertEquals("complex_primary_key", fk0.getReferencedTable().getInternalName()); - assertNotNull(fk0.getOnDelete()); - assertNotNull(fk0.getOnUpdate()); - } - - @Test - public void inspectTable_multiplePrimaryKey_succeeds() throws TableNotFoundException, SQLException { - - /* test */ - final TableDto response = schemaService.inspectTable(DATABASE_1_PRIVILEGED_DTO, "complex_primary_key"); - final ConstraintsDto constraints = response.getConstraints(); - final List<PrimaryKeyDto> primaryKey = new LinkedList<>(constraints.getPrimaryKey()); - assertEquals(2, primaryKey.size()); - final PrimaryKeyDto pk0 = primaryKey.get(0); - assertNull(pk0.getId()); - assertNotNull(pk0.getTable()); - assertNull(pk0.getTable().getId()); - assertEquals("complex_primary_key", pk0.getTable().getName()); - assertEquals("complex_primary_key", pk0.getTable().getInternalName()); - assertNotNull(pk0.getColumn()); - assertNull(pk0.getColumn().getId()); - assertNull(pk0.getColumn().getTableId()); - assertEquals(DATABASE_1_ID, pk0.getColumn().getDatabaseId()); - assertNull(pk0.getColumn().getAlias()); - assertEquals("id", pk0.getColumn().getName()); - assertEquals("id", pk0.getColumn().getInternalName()); - assertEquals(ColumnTypeDto.BIGINT, pk0.getColumn().getColumnType()); - final PrimaryKeyDto pk1 = primaryKey.get(1); - assertNull(pk1.getId()); - assertNotNull(pk1.getTable()); - assertNull(pk1.getTable().getId()); - assertEquals("complex_primary_key", pk1.getTable().getName()); - assertEquals("complex_primary_key", pk1.getTable().getInternalName()); - assertNotNull(pk1.getColumn()); - assertNull(pk1.getColumn().getId()); - assertNull(pk1.getColumn().getTableId()); - assertEquals(DATABASE_1_ID, pk1.getColumn().getDatabaseId()); - assertNull(pk1.getColumn().getAlias()); - assertEquals("other_id", pk1.getColumn().getName()); - assertEquals("other_id", pk1.getColumn().getInternalName()); - assertEquals(ColumnTypeDto.BIGINT, pk1.getColumn().getColumnType()); - } - - @Test - public void inspectTable_exoticBoolean_succeeds() throws TableNotFoundException, SQLException { - - /* test */ - final TableDto response = schemaService.inspectTable(DATABASE_1_PRIVILEGED_DTO, "exotic_boolean"); - final ConstraintsDto constraints = response.getConstraints(); - final List<PrimaryKeyDto> primaryKey = new LinkedList<>(constraints.getPrimaryKey()); - assertEquals(1, primaryKey.size()); - final PrimaryKeyDto pk0 = primaryKey.get(0); - assertNull(pk0.getId()); - assertNotNull(pk0.getTable()); - assertNull(pk0.getTable().getId()); - assertEquals("exotic_boolean", pk0.getTable().getName()); - assertEquals("exotic_boolean", pk0.getTable().getInternalName()); - assertNotNull(pk0.getColumn()); - assertNull(pk0.getColumn().getId()); - assertNull(pk0.getColumn().getTableId()); - assertEquals(DATABASE_1_ID, pk0.getColumn().getDatabaseId()); - assertNull(pk0.getColumn().getAlias()); - assertEquals("bool_default", pk0.getColumn().getName()); - assertEquals("bool_default", pk0.getColumn().getInternalName()); - assertEquals(ColumnTypeDto.BOOL, pk0.getColumn().getColumnType()); - final List<ColumnDto> columns = response.getColumns(); - assertEquals(3, columns.size()); - assertColumn(columns.get(0), null, null, DATABASE_1_ID, "bool_default", "bool_default", ColumnTypeDto.BOOL, null, 0L, false, null); - assertColumn(columns.get(1), null, null, DATABASE_1_ID, "bool_tinyint", "bool_tinyint", ColumnTypeDto.BOOL, null, 0L, false, null); - assertColumn(columns.get(2), null, null, DATABASE_1_ID, "bool_tinyint_unsigned", "bool_tinyint_unsigned", ColumnTypeDto.BOOL, null, 0L, false, null); - } - - @Test - public void inspectView_succeeds() throws SQLException, ViewNotFoundException { - - /* test */ - final ViewDto response = schemaService.inspectView(DATABASE_1_PRIVILEGED_DTO, "not_in_metadata_db2"); - assertEquals("not_in_metadata_db2", response.getInternalName()); - assertEquals("not_in_metadata_db2", response.getName()); - assertEquals(DATABASE_1_ID, response.getVdbid()); - assertEquals(DATABASE_1_OWNER, response.getOwner().getId()); - assertFalse(response.getIsInitialView()); - assertEquals(DATABASE_1_PUBLIC, response.getIsPublic()); - assertTrue(response.getQuery().length() >= 69); - assertNotNull(response.getQueryHash()); - assertEquals(4, response.getColumns().size()); - final ViewColumnDto column0 = response.getColumns().get(0); - assertNotNull(column0.getName()); - assertEquals("date", column0.getInternalName()); - assertEquals(DATABASE_1_ID, column0.getDatabaseId()); - final ViewColumnDto column1 = response.getColumns().get(1); - assertNotNull(column1.getName()); - assertEquals("location", column1.getInternalName()); - assertEquals(DATABASE_1_ID, column1.getDatabaseId()); - final ViewColumnDto column2 = response.getColumns().get(2); - assertNotNull(column2.getName()); - assertEquals("MinTemp", column2.getInternalName()); - assertEquals(DATABASE_1_ID, column2.getDatabaseId()); - final ViewColumnDto column3 = response.getColumns().get(3); - assertNotNull(column3.getName()); - assertEquals("Rainfall", column3.getInternalName()); - assertEquals(DATABASE_1_ID, column3.getDatabaseId()); - } - - protected static void assertViewColumn(ViewColumnDto column, ViewColumnDto other) { - assertNotNull(column); - assertNotNull(other); - assertEquals(column.getId(), other.getId()); - assertEquals(column.getDatabaseId(), other.getDatabaseId()); - assertEquals(column.getName(), other.getName()); - assertEquals(column.getInternalName(), other.getInternalName()); - assertEquals(column.getColumnType(), other.getColumnType()); - assertEquals(column.getSize(), other.getSize()); - assertEquals(column.getD(), other.getD()); - assertEquals(column.getIsNullAllowed(), other.getIsNullAllowed()); - assertEquals(column.getDescription(), other.getDescription()); - } - - protected static void assertColumn(ColumnDto column, Long id, Long tableId, Long databaseId, String name, - String internalName, ColumnTypeDto type, Long size, Long d, Boolean nullAllowed, - String description) { - log.trace("assert column: {}", internalName); - assertNotNull(column); - assertEquals(id, column.getId()); - assertEquals(tableId, column.getTableId()); - assertEquals(databaseId, column.getDatabaseId()); - assertEquals(name, column.getName()); - assertEquals(internalName, column.getInternalName()); - assertEquals(type, column.getColumnType()); - assertEquals(size, column.getSize()); - assertEquals(d, column.getD()); - assertEquals(nullAllowed, column.getIsNullAllowed()); - assertEquals(description, column.getDescription()); - } - -} diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/SubsetServiceIntegrationTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/SubsetServiceIntegrationTest.java index 0eab9c6ff3..886d492cd7 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/SubsetServiceIntegrationTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/SubsetServiceIntegrationTest.java @@ -21,7 +21,6 @@ import org.testcontainers.junit.jupiter.Testcontainers; import java.sql.SQLException; import java.util.List; -import java.util.Map; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.when; @@ -45,8 +44,8 @@ public class SubsetServiceIntegrationTest extends AbstractUnitTest { public void beforeEach() throws SQLException { genesis(); /* metadata database */ - MariaDbConfig.dropDatabase(CONTAINER_1_PRIVILEGED_DTO, DATABASE_1_INTERNALNAME); - MariaDbConfig.createInitDatabase(CONTAINER_1_PRIVILEGED_DTO, DATABASE_1_DTO); + MariaDbConfig.dropDatabase(CONTAINER_1_DTO, DATABASE_1_INTERNALNAME); + MariaDbConfig.createInitDatabase(CONTAINER_1_DTO, DATABASE_1_DTO); } @Test @@ -108,7 +107,7 @@ public class SubsetServiceIntegrationTest extends AbstractUnitTest { /* test */ persist_generic(QUERY_2_ID, List.of(IDENTIFIER_5_BRIEF_DTO), true); - final QueryDto response = queryService.findById(DATABASE_1_PRIVILEGED_DTO, QUERY_2_ID); + final QueryDto response = queryService.findById(DATABASE_1_DTO, QUERY_2_ID); assertEquals(2L, response.getId()); assertTrue(response.getIsPersisted()); } @@ -124,30 +123,11 @@ public class SubsetServiceIntegrationTest extends AbstractUnitTest { /* test */ persist_generic(QUERY_1_ID, List.of(IDENTIFIER_2_BRIEF_DTO), false); - final QueryDto response = queryService.findById(DATABASE_1_PRIVILEGED_DTO, QUERY_1_ID); + final QueryDto response = queryService.findById(DATABASE_1_DTO, QUERY_1_ID); assertEquals(1L, response.getId()); assertFalse(response.getIsPersisted()); } - @Test - public void createQueryStore_succeeds() throws SQLException, QueryStoreCreateException, InterruptedException { - - /* mock */ - MariaDbConfig.dropQueryStore(DATABASE_1_PRIVILEGED_DTO); - - /* test */ - createQueryStore_generic(DATABASE_1_INTERNALNAME); - } - - @Test - public void createQueryStore_fails() { - - /* test */ - assertThrows(QueryStoreCreateException.class, () -> { - createQueryStore_generic(DATABASE_1_INTERNALNAME); - }); - } - protected void findById_generic(Long queryId) throws RemoteUnavailableException, SQLException, UserNotFoundException, QueryNotFoundException, MetadataServiceException, DatabaseNotFoundException, InterruptedException { @@ -160,10 +140,10 @@ public class SubsetServiceIntegrationTest extends AbstractUnitTest { .thenReturn(List.of(IDENTIFIER_2_BRIEF_DTO)); when(metadataServiceGateway.getUserById(USER_1_ID)) .thenReturn(USER_1_DTO); - MariaDbConfig.insertQueryStore(DATABASE_1_PRIVILEGED_DTO, QUERY_1_DTO, USER_1_ID); + MariaDbConfig.insertQueryStore(DATABASE_1_DTO, QUERY_1_DTO, USER_1_ID); /* test */ - final QueryDto response = queryService.findById(DATABASE_1_PRIVILEGED_DTO, queryId); + final QueryDto response = queryService.findById(DATABASE_1_DTO, queryId); assertEquals(QUERY_1_ID, response.getId()); } @@ -175,13 +155,13 @@ public class SubsetServiceIntegrationTest extends AbstractUnitTest { Thread.sleep(1000) /* wait for test container some more */; /* mock */ - MariaDbConfig.insertQueryStore(DATABASE_1_PRIVILEGED_DTO, QUERY_1_DTO, USER_1_ID); - MariaDbConfig.insertQueryStore(DATABASE_1_PRIVILEGED_DTO, QUERY_2_DTO, USER_1_ID); + MariaDbConfig.insertQueryStore(DATABASE_1_DTO, QUERY_1_DTO, USER_1_ID); + MariaDbConfig.insertQueryStore(DATABASE_1_DTO, QUERY_2_DTO, USER_1_ID); when(metadataServiceGateway.getIdentifiers(DATABASE_1_ID, null)) .thenReturn(List.of(IDENTIFIER_2_BRIEF_DTO, IDENTIFIER_5_BRIEF_DTO)); /* test */ - return queryService.findAll(DATABASE_1_PRIVILEGED_DTO, filterPersisted); + return queryService.findAll(DATABASE_1_DTO, filterPersisted); } protected void persist_generic(Long queryId, List<IdentifierBriefDto> identifiers, Boolean persist) @@ -194,23 +174,11 @@ public class SubsetServiceIntegrationTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getIdentifiers(DATABASE_1_ID, queryId)) .thenReturn(identifiers); - MariaDbConfig.insertQueryStore(DATABASE_1_PRIVILEGED_DTO, QUERY_1_DTO, USER_1_ID); - MariaDbConfig.insertQueryStore(DATABASE_1_PRIVILEGED_DTO, QUERY_2_DTO, USER_1_ID); - - /* test */ - queryService.persist(DATABASE_1_PRIVILEGED_DTO, queryId, persist); - } - - protected void createQueryStore_generic(String databaseName) throws SQLException, QueryStoreCreateException, - InterruptedException { - - /* pre-condition */ - Thread.sleep(1000) /* wait for test container some more */; + MariaDbConfig.insertQueryStore(DATABASE_1_DTO, QUERY_1_DTO, USER_1_ID); + MariaDbConfig.insertQueryStore(DATABASE_1_DTO, QUERY_2_DTO, USER_1_ID); /* test */ - queryService.createQueryStore(CONTAINER_1_PRIVILEGED_DTO, databaseName); - final List<Map<String, Object>> response = MariaDbConfig.listQueryStore(DATABASE_1_PRIVILEGED_DTO); - assertEquals(0, response.size()); + queryService.persist(DATABASE_1_DTO, queryId, persist); } } diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/TableServiceIntegrationTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/TableServiceIntegrationTest.java index 383c44770f..f8687d8e0d 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/TableServiceIntegrationTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/TableServiceIntegrationTest.java @@ -2,18 +2,7 @@ package at.tuwien.service; import at.tuwien.api.database.query.ImportDto; import at.tuwien.api.database.table.*; -import at.tuwien.api.database.table.columns.ColumnCreateDto; -import at.tuwien.api.database.table.columns.ColumnDto; import at.tuwien.api.database.table.columns.ColumnStatisticDto; -import at.tuwien.api.database.table.columns.ColumnTypeDto; -import at.tuwien.api.database.table.constraints.ConstraintsCreateDto; -import at.tuwien.api.database.table.constraints.ConstraintsDto; -import at.tuwien.api.database.table.constraints.foreign.ForeignKeyCreateDto; -import at.tuwien.api.database.table.constraints.foreign.ForeignKeyDto; -import at.tuwien.api.database.table.constraints.foreign.ForeignKeyReferenceDto; -import at.tuwien.api.database.table.constraints.primary.PrimaryKeyDto; -import at.tuwien.api.database.table.constraints.unique.UniqueDto; -import at.tuwien.api.database.table.internal.TableCreateDto; import at.tuwien.config.MariaDbConfig; import at.tuwien.config.MariaDbContainerConfig; import at.tuwien.config.S3Config; @@ -22,7 +11,10 @@ import at.tuwien.gateway.MetadataServiceGateway; import at.tuwien.test.AbstractUnitTest; import com.google.common.io.Files; import lombok.extern.log4j.Log4j2; -import org.junit.jupiter.api.*; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -44,9 +36,11 @@ import java.io.IOException; import java.math.BigDecimal; import java.sql.SQLException; import java.time.Instant; -import java.util.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; -import static at.tuwien.service.SchemaServiceIntegrationTest.assertColumn; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.when; @@ -88,11 +82,11 @@ public class TableServiceIntegrationTest extends AbstractUnitTest { public void beforeEach() throws SQLException { genesis(); /* metadata database */ - MariaDbConfig.dropDatabase(CONTAINER_1_PRIVILEGED_DTO, DATABASE_1_INTERNALNAME); - MariaDbConfig.dropDatabase(CONTAINER_1_PRIVILEGED_DTO, DATABASE_2_INTERNALNAME); - MariaDbConfig.dropDatabase(CONTAINER_1_PRIVILEGED_DTO, DATABASE_3_INTERNALNAME); - MariaDbConfig.createInitDatabase(CONTAINER_1_PRIVILEGED_DTO, DATABASE_1_DTO); - MariaDbConfig.createInitDatabase(CONTAINER_1_PRIVILEGED_DTO, DATABASE_3_DTO); + MariaDbConfig.dropDatabase(CONTAINER_1_DTO, DATABASE_1_INTERNALNAME); + MariaDbConfig.dropDatabase(CONTAINER_1_DTO, DATABASE_2_INTERNALNAME); + MariaDbConfig.dropDatabase(CONTAINER_1_DTO, DATABASE_3_INTERNALNAME); + MariaDbConfig.createInitDatabase(CONTAINER_1_DTO, DATABASE_1_DTO); + MariaDbConfig.createInitDatabase(CONTAINER_1_DTO, DATABASE_3_DTO); /* s3 */ if (s3Client.listBuckets().buckets().stream().noneMatch(b -> b.name().equals(s3Config.getS3Bucket()))) { s3Client.createBucket(CreateBucketRequest.builder() @@ -124,13 +118,13 @@ public class TableServiceIntegrationTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getContainerById(CONTAINER_1_ID)) - .thenReturn(CONTAINER_1_PRIVILEGED_DTO); + .thenReturn(CONTAINER_1_DTO); when(metadataServiceGateway.getTableById(DATABASE_1_ID, TABLE_1_ID)) - .thenReturn(TABLE_1_PRIVILEGED_DTO); + .thenReturn(TABLE_1_DTO); /* test */ - tableService.updateTuple(TABLE_1_PRIVILEGED_DTO, request); - final List<Map<String, String>> result = MariaDbConfig.selectQuery(DATABASE_1_PRIVILEGED_DTO, "SELECT id, `date`, location, mintemp, rainfall FROM weather_aus WHERE id = 1", Set.of("id", "date", "location", "mintemp", "rainfall")); + tableService.updateTuple(TABLE_1_DTO, request); + final List<Map<String, String>> result = MariaDbConfig.selectQuery(DATABASE_1_DTO, "SELECT id, `date`, location, mintemp, rainfall FROM weather_aus WHERE id = 1", Set.of("id", "date", "location", "mintemp", "rainfall")); assertEquals("1", result.get(0).get("id")); assertEquals("2023-10-03", result.get(0).get("date")); // <<< assertEquals("Vienna", result.get(0).get("location")); // <<< @@ -158,13 +152,13 @@ public class TableServiceIntegrationTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getContainerById(CONTAINER_1_ID)) - .thenReturn(CONTAINER_1_PRIVILEGED_DTO); + .thenReturn(CONTAINER_1_DTO); when(metadataServiceGateway.getTableById(DATABASE_1_ID, TABLE_1_ID)) - .thenReturn(TABLE_1_PRIVILEGED_DTO); + .thenReturn(TABLE_1_DTO); /* test */ - tableService.updateTuple(TABLE_1_PRIVILEGED_DTO, request); - final List<Map<String, String>> result = MariaDbConfig.selectQuery(DATABASE_1_PRIVILEGED_DTO, "SELECT id, `date`, location, mintemp, rainfall FROM weather_aus WHERE id = 4", Set.of("id", "date", "location", "mintemp", "rainfall")); + tableService.updateTuple(TABLE_1_DTO, request); + final List<Map<String, String>> result = MariaDbConfig.selectQuery(DATABASE_1_DTO, "SELECT id, `date`, location, mintemp, rainfall FROM weather_aus WHERE id = 4", Set.of("id", "date", "location", "mintemp", "rainfall")); assertEquals("4", result.get(0).get("id")); assertEquals("2023-10-03", result.get(0).get("date")); // <<< assertEquals("Vienna", result.get(0).get("location")); // <<< @@ -191,13 +185,13 @@ public class TableServiceIntegrationTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getContainerById(CONTAINER_1_ID)) - .thenReturn(CONTAINER_1_PRIVILEGED_DTO); + .thenReturn(CONTAINER_1_DTO); when(metadataServiceGateway.getTableById(DATABASE_1_ID, TABLE_1_ID)) - .thenReturn(TABLE_1_PRIVILEGED_DTO); + .thenReturn(TABLE_1_DTO); /* test */ - tableService.updateTuple(TABLE_1_PRIVILEGED_DTO, request); - final List<Map<String, String>> result = MariaDbConfig.selectQuery(DATABASE_1_PRIVILEGED_DTO, "SELECT id, `date`, location, mintemp, rainfall FROM weather_aus WHERE id = 1", Set.of("id", "date", "location", "mintemp", "rainfall")); + tableService.updateTuple(TABLE_1_DTO, request); + final List<Map<String, String>> result = MariaDbConfig.selectQuery(DATABASE_1_DTO, "SELECT id, `date`, location, mintemp, rainfall FROM weather_aus WHERE id = 1", Set.of("id", "date", "location", "mintemp", "rainfall")); assertEquals("1", result.get(0).get("id")); assertEquals("2023-10-03", result.get(0).get("date")); // <<< assertEquals("Vienna", result.get(0).get("location")); // <<< @@ -224,13 +218,13 @@ public class TableServiceIntegrationTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getContainerById(CONTAINER_1_ID)) - .thenReturn(CONTAINER_1_PRIVILEGED_DTO); + .thenReturn(CONTAINER_1_DTO); when(metadataServiceGateway.getTableById(DATABASE_1_ID, TABLE_1_ID)) - .thenReturn(TABLE_1_PRIVILEGED_DTO); + .thenReturn(TABLE_1_DTO); /* test */ - tableService.updateTuple(TABLE_1_PRIVILEGED_DTO, request); - final List<Map<String, String>> result = MariaDbConfig.selectQuery(DATABASE_1_PRIVILEGED_DTO, "SELECT id, `date`, location, mintemp, rainfall FROM weather_aus WHERE id = 1", Set.of("id", "date", "location", "mintemp", "rainfall")); + tableService.updateTuple(TABLE_1_DTO, request); + final List<Map<String, String>> result = MariaDbConfig.selectQuery(DATABASE_1_DTO, "SELECT id, `date`, location, mintemp, rainfall FROM weather_aus WHERE id = 1", Set.of("id", "date", "location", "mintemp", "rainfall")); assertEquals("1", result.get(0).get("id")); assertEquals("2023-10-03", result.get(0).get("date")); // <<< assertEquals("Vienna", result.get(0).get("location")); // <<< @@ -255,13 +249,13 @@ public class TableServiceIntegrationTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getContainerById(CONTAINER_1_ID)) - .thenReturn(CONTAINER_1_PRIVILEGED_DTO); + .thenReturn(CONTAINER_1_DTO); when(metadataServiceGateway.getTableById(DATABASE_1_ID, TABLE_1_ID)) - .thenReturn(TABLE_1_PRIVILEGED_DTO); + .thenReturn(TABLE_1_DTO); /* test */ - tableService.createTuple(TABLE_1_PRIVILEGED_DTO, request); - final List<Map<String, String>> result = MariaDbConfig.selectQuery(DATABASE_1_PRIVILEGED_DTO, "SELECT id, `date`, location, mintemp, rainfall FROM weather_aus WHERE id = 4", Set.of("id", "date", "location", "mintemp", "rainfall")); + tableService.createTuple(TABLE_1_DTO, request); + final List<Map<String, String>> result = MariaDbConfig.selectQuery(DATABASE_1_DTO, "SELECT id, `date`, location, mintemp, rainfall FROM weather_aus WHERE id = 4", Set.of("id", "date", "location", "mintemp", "rainfall")); assertEquals("4", result.get(0).get("id")); assertEquals("2023-10-03", result.get(0).get("date")); assertEquals("Vienna", result.get(0).get("location")); @@ -283,17 +277,17 @@ public class TableServiceIntegrationTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getContainerById(CONTAINER_1_ID)) - .thenReturn(CONTAINER_1_PRIVILEGED_DTO); + .thenReturn(CONTAINER_1_DTO); when(metadataServiceGateway.getTableById(DATABASE_3_ID, TABLE_8_ID)) - .thenReturn(TABLE_8_PRIVILEGED_DTO); + .thenReturn(TABLE_8_DTO); s3Client.putObject(PutObjectRequest.builder() .key("s3key") .bucket(s3Config.getS3Bucket()) .build(), RequestBody.fromFile(new File("src/test/resources/csv/keyboard.csv"))); /* test */ - tableService.createTuple(TABLE_8_PRIVILEGED_DTO, request); - final List<Map<String, byte[]>> result = MariaDbConfig.selectQueryByteArr(DATABASE_3_PRIVILEGED_DTO, "SELECT raw FROM mfcc WHERE raw IS NOT NULL", Set.of("raw")); + tableService.createTuple(TABLE_8_DTO, request); + final List<Map<String, byte[]>> result = MariaDbConfig.selectQueryByteArr(DATABASE_3_DTO, "SELECT raw FROM mfcc WHERE raw IS NOT NULL", Set.of("raw")); assertNotNull(result.get(0).get("raw")); assertArrayEquals(Files.toByteArray(new File("src/test/resources/csv/keyboard.csv")), result.get(0).get("raw")); } @@ -315,13 +309,13 @@ public class TableServiceIntegrationTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getContainerById(CONTAINER_1_ID)) - .thenReturn(CONTAINER_1_PRIVILEGED_DTO); + .thenReturn(CONTAINER_1_DTO); when(metadataServiceGateway.getTableById(DATABASE_1_ID, TABLE_1_ID)) - .thenReturn(TABLE_1_PRIVILEGED_DTO); + .thenReturn(TABLE_1_DTO); /* test */ - tableService.createTuple(TABLE_1_PRIVILEGED_DTO, request); - final List<Map<String, String>> result = MariaDbConfig.selectQuery(DATABASE_1_PRIVILEGED_DTO, "SELECT id, `date`, location, mintemp, rainfall FROM weather_aus WHERE id = 4", Set.of("id", "date", "location", "mintemp", "rainfall")); + tableService.createTuple(TABLE_1_DTO, request); + final List<Map<String, String>> result = MariaDbConfig.selectQuery(DATABASE_1_DTO, "SELECT id, `date`, location, mintemp, rainfall FROM weather_aus WHERE id = 4", Set.of("id", "date", "location", "mintemp", "rainfall")); assertEquals("4", result.get(0).get("id")); assertEquals("2023-10-03", result.get(0).get("date")); assertEquals("Vienna", result.get(0).get("location")); @@ -341,13 +335,13 @@ public class TableServiceIntegrationTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getContainerById(CONTAINER_1_ID)) - .thenReturn(CONTAINER_1_PRIVILEGED_DTO); + .thenReturn(CONTAINER_1_DTO); when(metadataServiceGateway.getTableById(DATABASE_1_ID, TABLE_1_ID)) - .thenReturn(TABLE_1_PRIVILEGED_DTO); + .thenReturn(TABLE_1_DTO); /* test */ - tableService.deleteTuple(TABLE_1_PRIVILEGED_DTO, request); - final List<Map<String, String>> result = MariaDbConfig.selectQuery(DATABASE_1_PRIVILEGED_DTO, "SELECT id FROM weather_aus WHERE id = 1", Set.of("id")); + tableService.deleteTuple(TABLE_1_DTO, request); + final List<Map<String, String>> result = MariaDbConfig.selectQuery(DATABASE_1_DTO, "SELECT id FROM weather_aus WHERE id = 1", Set.of("id")); assertEquals(0, result.size()); } @@ -365,173 +359,22 @@ public class TableServiceIntegrationTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getContainerById(CONTAINER_1_ID)) - .thenReturn(CONTAINER_1_PRIVILEGED_DTO); + .thenReturn(CONTAINER_1_DTO); when(metadataServiceGateway.getTableById(DATABASE_1_ID, TABLE_1_ID)) - .thenReturn(TABLE_1_PRIVILEGED_DTO); + .thenReturn(TABLE_1_DTO); /* test */ - tableService.deleteTuple(TABLE_1_PRIVILEGED_DTO, request); - final List<Map<String, String>> result = MariaDbConfig.selectQuery(DATABASE_1_PRIVILEGED_DTO, "SELECT id FROM weather_aus WHERE id = 1", Set.of("id")); + tableService.deleteTuple(TABLE_1_DTO, request); + final List<Map<String, String>> result = MariaDbConfig.selectQuery(DATABASE_1_DTO, "SELECT id FROM weather_aus WHERE id = 1", Set.of("id")); assertEquals(0, result.size()); } - @Test - public void getSchemas_succeeds() throws TableNotFoundException, SQLException, DatabaseMalformedException { - - /* test */ - final List<TableDto> response = tableService.getSchemas(DATABASE_1_PRIVILEGED_DTO); - assertEquals(4, response.size()); - final TableDto table0 = response.get(0); - Assertions.assertEquals("complex_foreign_keys", table0.getInternalName()); - Assertions.assertEquals("complex_foreign_keys", table0.getName()); - Assertions.assertEquals(DATABASE_1_ID, table0.getTdbid()); - assertTrue(table0.getIsVersioned()); - Assertions.assertEquals(DATABASE_1_PUBLIC, table0.getIsPublic()); - final List<ColumnDto> columns0 = table0.getColumns(); - assertNotNull(columns0); - Assertions.assertEquals(3, columns0.size()); - assertColumn(columns0.get(0), null, null, DATABASE_1_ID, "id", "id", ColumnTypeDto.BIGINT, 19L, 0L, false, null); - assertColumn(columns0.get(1), null, null, DATABASE_1_ID, "weather_id", "weather_id", ColumnTypeDto.BIGINT, 19L, 0L, false, null); - assertColumn(columns0.get(2), null, null, DATABASE_1_ID, "other_id", "other_id", ColumnTypeDto.BIGINT, 19L, 0L, false, null); - final ConstraintsDto constraints0 = table0.getConstraints(); - assertNotNull(constraints0); - assertEquals(1, constraints0.getPrimaryKey().size()); - final PrimaryKeyDto pk0 = new LinkedList<>(constraints0.getPrimaryKey()).get(0); - assertNull(pk0.getId()); - assertNull(pk0.getColumn().getId()); - assertEquals("id", pk0.getColumn().getName()); - assertEquals("id", pk0.getColumn().getInternalName()); - assertEquals(1, constraints0.getForeignKeys().size()); - final ForeignKeyDto fk0 = constraints0.getForeignKeys().get(0); - assertNotNull(fk0.getName()); - assertNull(fk0.getTable().getId()); - assertEquals("complex_foreign_keys", fk0.getTable().getName()); - assertEquals("complex_foreign_keys", fk0.getTable().getInternalName()); - assertNull(fk0.getReferencedTable().getId()); - assertEquals("complex_primary_key", fk0.getReferencedTable().getName()); - assertEquals("complex_primary_key", fk0.getReferencedTable().getInternalName()); - assertEquals(2, fk0.getReferences().size()); - final ForeignKeyReferenceDto fk0r0 = fk0.getReferences().get(0); - assertEquals("weather_id", fk0r0.getColumn().getName()); - assertEquals("weather_id", fk0r0.getColumn().getInternalName()); - assertNotNull(fk0r0.getColumn().getName()); - assertNotNull(fk0r0.getForeignKey()); - assertEquals("id", fk0r0.getReferencedColumn().getName()); - assertEquals("id", fk0r0.getReferencedColumn().getInternalName()); - final ForeignKeyReferenceDto fk0r1 = fk0.getReferences().get(1); - assertEquals("other_id", fk0r1.getColumn().getName()); - assertEquals("other_id", fk0r1.getColumn().getInternalName()); - assertNotNull(fk0r1.getColumn().getName()); - assertNotNull(fk0r1.getForeignKey()); - assertEquals("other_id", fk0r1.getReferencedColumn().getName()); - assertEquals("other_id", fk0r1.getReferencedColumn().getInternalName()); - assertEquals(0, constraints0.getChecks().size()); - assertEquals(0, constraints0.getUniques().size()); - /* table 1 */ - final TableDto table1 = response.get(1); - Assertions.assertEquals("complex_primary_key", table1.getInternalName()); - Assertions.assertEquals("complex_primary_key", table1.getName()); - Assertions.assertEquals(DATABASE_1_ID, table1.getTdbid()); - assertTrue(table1.getIsVersioned()); - Assertions.assertEquals(DATABASE_1_PUBLIC, table1.getIsPublic()); - final List<ColumnDto> columns1 = table1.getColumns(); - assertNotNull(columns1); - Assertions.assertEquals(2, columns1.size()); - assertColumn(columns1.get(0), null, null, DATABASE_1_ID, "id", "id", ColumnTypeDto.BIGINT, 19L, 0L, false, null); - assertColumn(columns1.get(1), null, null, DATABASE_1_ID, "other_id", "other_id", ColumnTypeDto.BIGINT, 19L, 0L, false, null); - final ConstraintsDto constraints1 = table1.getConstraints(); - assertNotNull(constraints1); - assertEquals(2, constraints1.getPrimaryKey().size()); - final PrimaryKeyDto pk10 = new LinkedList<>(constraints1.getPrimaryKey()).get(0); - assertNull(pk10.getId()); - assertNull(pk10.getColumn().getId()); - assertEquals("id", pk10.getColumn().getName()); - assertEquals("id", pk10.getColumn().getInternalName()); - final PrimaryKeyDto pk11 = new LinkedList<>(constraints1.getPrimaryKey()).get(1); - assertNull(pk11.getId()); - assertNull(pk11.getColumn().getId()); - assertEquals("other_id", pk11.getColumn().getName()); - assertEquals("other_id", pk11.getColumn().getInternalName()); - assertEquals(0, constraints1.getForeignKeys().size()); - assertEquals(0, constraints1.getChecks().size()); - assertEquals(0, constraints1.getUniques().size()); - /* table 2 */ - final TableDto table2 = response.get(2); - Assertions.assertEquals("exotic_boolean", table2.getInternalName()); - Assertions.assertEquals("exotic_boolean", table2.getName()); - Assertions.assertEquals(DATABASE_1_ID, table2.getTdbid()); - assertTrue(table2.getIsVersioned()); - Assertions.assertEquals(DATABASE_1_PUBLIC, table2.getIsPublic()); - final List<ColumnDto> columns2 = table2.getColumns(); - assertNotNull(columns2); - Assertions.assertEquals(3, columns2.size()); - assertColumn(columns2.get(0), null, null, DATABASE_1_ID, "bool_default", "bool_default", ColumnTypeDto.BOOL, null, 0L, false, null); - assertColumn(columns2.get(1), null, null, DATABASE_1_ID, "bool_tinyint", "bool_tinyint", ColumnTypeDto.BOOL, null, 0L, false, null); - assertColumn(columns2.get(2), null, null, DATABASE_1_ID, "bool_tinyint_unsigned", "bool_tinyint_unsigned", ColumnTypeDto.BOOL, null, 0L, false, null); - final ConstraintsDto constraints2 = table2.getConstraints(); - assertNotNull(constraints2); - final Set<PrimaryKeyDto> primaryKey2 = constraints2.getPrimaryKey(); - Assertions.assertEquals(1, primaryKey2.size()); - final Set<String> checks2 = constraints2.getChecks(); - Assertions.assertEquals(0, checks2.size()); - final List<UniqueDto> uniques2 = constraints2.getUniques(); - Assertions.assertEquals(0, uniques2.size()); - /* table 3 */ - final TableDto table3 = response.get(3); - Assertions.assertEquals("not_in_metadata_db", table3.getInternalName()); - Assertions.assertEquals("not_in_metadata_db", table3.getName()); - Assertions.assertEquals(DATABASE_1_ID, table3.getTdbid()); - assertTrue(table3.getIsVersioned()); - Assertions.assertEquals(DATABASE_1_PUBLIC, table3.getIsPublic()); - final List<ColumnDto> columns3 = table3.getColumns(); - assertNotNull(columns3); - Assertions.assertEquals(5, columns3.size()); - assertColumn(columns3.get(0), null, null, DATABASE_1_ID, "id", "id", ColumnTypeDto.BIGINT, 19L, 0L, false, null); - assertColumn(columns3.get(1), null, null, DATABASE_1_ID, "given_name", "given_name", ColumnTypeDto.VARCHAR, 255L, null, false, null); - assertColumn(columns3.get(2), null, null, DATABASE_1_ID, "middle_name", "middle_name", ColumnTypeDto.VARCHAR, 255L, null, true, null); - assertColumn(columns3.get(3), null, null, DATABASE_1_ID, "family_name", "family_name", ColumnTypeDto.VARCHAR, 255L, null, false, null); - assertColumn(columns3.get(4), null, null, DATABASE_1_ID, "age", "age", ColumnTypeDto.INT, 10L, 0L, false, null); - final ConstraintsDto constraints3 = table3.getConstraints(); - assertNotNull(constraints3); - final Set<PrimaryKeyDto> primaryKey3 = constraints3.getPrimaryKey(); - Assertions.assertEquals(1, primaryKey3.size()); - final Set<String> checks3 = constraints3.getChecks(); - Assertions.assertEquals(1, checks3.size()); - Assertions.assertEquals(Set.of("`age` > 0 and `age` < 120"), checks3); - final List<UniqueDto> uniques3 = constraints3.getUniques(); - Assertions.assertEquals(1, uniques3.size()); - Assertions.assertEquals(2, uniques3.get(0).getColumns().size()); - Assertions.assertEquals("not_in_metadata_db", uniques3.get(0).getTable().getInternalName()); - Assertions.assertEquals("given_name", uniques3.get(0).getColumns().get(0).getInternalName()); - Assertions.assertEquals("family_name", uniques3.get(0).getColumns().get(1).getInternalName()); - } - - @Test - public void create_succeeds() throws TableNotFoundException, TableMalformedException, SQLException, - TableExistsException { - - /* test */ - final TableDto response = tableService.createTable(DATABASE_1_PRIVILEGED_DTO, TABLE_4_CREATE_INTERNAL_DTO); - assertEquals(TABLE_4_NAME, response.getName()); - assertEquals(TABLE_4_INTERNALNAME, response.getInternalName()); - final List<ColumnDto> columns = response.getColumns(); - assertEquals(TABLE_4_COLUMNS.size(), columns.size()); - assertColumn(columns.get(0), null, null, DATABASE_1_ID, "timestamp", "timestamp", ColumnTypeDto.TIMESTAMP, null, null, false, null); - assertColumn(columns.get(1), null, null, DATABASE_1_ID, "value", "value", ColumnTypeDto.DECIMAL, 10L, 10L, true, null); - final ConstraintsDto constraints = response.getConstraints(); - assertNotNull(constraints); - final Set<PrimaryKeyDto> primaryKey = constraints.getPrimaryKey(); - Assertions.assertEquals(1, primaryKey.size()); - final Set<String> checks = constraints.getChecks(); - Assertions.assertEquals(0, checks.size()); - } - @Test @Disabled("Not stable CI/CD") public void getStatistics_succeeds() throws TableMalformedException, SQLException, TableNotFoundException { /* test */ - final TableStatisticDto response = tableService.getStatistics(TABLE_2_PRIVILEGED_DTO); + final TableStatisticDto response = tableService.getStatistics(DATABASE_1_DTO, TABLE_2_DTO); assertEquals(TABLE_2_COLUMNS.size(), response.getColumns().size()); log.trace("response rows: {}", response.getRows()); assertEquals(3L, response.getRows()); @@ -556,116 +399,22 @@ public class TableServiceIntegrationTest extends AbstractUnitTest { assertNotNull(column4.getStdDev()); } - @Test - public void create_malformed_fails() { - final at.tuwien.api.database.table.internal.TableCreateDto request = TableCreateDto.builder() - .name("missing_foreign_key") - .columns(List.of()) - .constraints(ConstraintsCreateDto.builder() - .foreignKeys(List.of(ForeignKeyCreateDto.builder() - .columns(List.of("i_do_not_exist")) - .referencedTable("neither_do_i") - .referencedColumns(List.of("behold")) - .build())) - .build()) - .build(); - - /* test */ - assertThrows(TableMalformedException.class, () -> { - tableService.createTable(DATABASE_1_PRIVILEGED_DTO, request); - }); - } - - @Test - public void create_compositePrimaryKey_fails() throws TableNotFoundException, TableMalformedException, SQLException, - TableExistsException { - final at.tuwien.api.database.table.internal.TableCreateDto request = TableCreateDto.builder() - .name("composite_primary_key") - .columns(List.of(ColumnCreateDto.builder() - .name("name") - .type(ColumnTypeDto.VARCHAR) - .size(255L) - .nullAllowed(false) - .build(), - ColumnCreateDto.builder() - .name("lat") - .type(ColumnTypeDto.DECIMAL) - .size(10L) - .d(10L) - .nullAllowed(false) - .build(), - ColumnCreateDto.builder() - .name("lng") - .type(ColumnTypeDto.DECIMAL) - .size(10L) - .d(10L) - .nullAllowed(false) - .build())) - .constraints(ConstraintsCreateDto.builder() - .primaryKey(Set.of("lat", "lng")) - .foreignKeys(List.of()) - .checks(Set.of()) - .uniques(List.of()) - .build()) - .build(); - - /* test */ - final TableDto response = tableService.createTable(DATABASE_1_PRIVILEGED_DTO, request); - assertEquals("composite_primary_key", response.getName()); - assertEquals("composite_primary_key", response.getInternalName()); - final List<ColumnDto> columns = response.getColumns(); - assertEquals(3, columns.size()); - assertColumn(columns.get(0), null, null, DATABASE_1_ID, "name", "name", ColumnTypeDto.VARCHAR, 255L, null, false, null); - assertColumn(columns.get(1), null, null, DATABASE_1_ID, "lat", "lat", ColumnTypeDto.DECIMAL, 10L, 10L, false, null); - assertColumn(columns.get(2), null, null, DATABASE_1_ID, "lng", "lng", ColumnTypeDto.DECIMAL, 10L, 10L, false, null); - final ConstraintsDto constraints = response.getConstraints(); - assertNotNull(constraints); - final Set<String> checks = constraints.getChecks(); - assertNotNull(checks); - assertEquals(0, checks.size()); - final List<PrimaryKeyDto> primaryKeys = new LinkedList<>(constraints.getPrimaryKey()); - assertNotNull(primaryKeys); - assertEquals(2, primaryKeys.size()); - assertEquals("lat", primaryKeys.get(0).getColumn().getInternalName()); - assertEquals("lng", primaryKeys.get(1).getColumn().getInternalName()); - final List<ForeignKeyDto> foreignKeys = constraints.getForeignKeys(); - assertNotNull(foreignKeys); - assertEquals(0, foreignKeys.size()); - final List<UniqueDto> uniques = constraints.getUniques(); - assertNotNull(uniques); - assertEquals(0, uniques.size()); - } - - @Test - public void create_needSequence_succeeds() throws TableNotFoundException, TableMalformedException, SQLException, - TableExistsException { - - /* mock */ - MariaDbConfig.dropTable(DATABASE_1_PRIVILEGED_DTO, TABLE_1_INTERNAL_NAME); - - /* test */ - final TableDto response = tableService.createTable(DATABASE_1_PRIVILEGED_DTO, TABLE_1_CREATE_INTERNAL_DTO); - assertEquals(TABLE_1_NAME, response.getName()); - assertEquals(TABLE_1_INTERNAL_NAME, response.getInternalName()); - assertEquals(TABLE_1_COLUMNS.size(), response.getColumns().size()); - } - @Test public void delete_succeeds() throws SQLException, QueryMalformedException { /* test */ - tableService.delete(TABLE_1_PRIVILEGED_DTO); + tableService.delete(TABLE_1_DTO); } @Test public void delete_notFound_fails() throws SQLException { /* mock */ - MariaDbConfig.createDatabase(CONTAINER_1_PRIVILEGED_DTO, DATABASE_2_INTERNALNAME); + MariaDbConfig.createDatabase(CONTAINER_1_DTO, DATABASE_2_INTERNALNAME); /* test */ assertThrows(QueryMalformedException.class, () -> { - tableService.delete(TABLE_5_PRIVILEGED_DTO); + tableService.delete(TABLE_5_DTO); }); } @@ -673,7 +422,7 @@ public class TableServiceIntegrationTest extends AbstractUnitTest { public void getCount_succeeds() throws SQLException, QueryMalformedException { /* test */ - final Long response = tableService.getCount(TABLE_1_PRIVILEGED_DTO, null); + final Long response = tableService.getCount(TABLE_1_DTO, null); assertEquals(3, response); } @@ -681,7 +430,7 @@ public class TableServiceIntegrationTest extends AbstractUnitTest { public void getCount_timestamp_succeeds() throws SQLException, QueryMalformedException { /* test */ - final Long response = tableService.getCount(TABLE_1_PRIVILEGED_DTO, Instant.ofEpochSecond(0)); + final Long response = tableService.getCount(TABLE_1_DTO, Instant.ofEpochSecond(0)); assertEquals(0, response); } @@ -689,11 +438,11 @@ public class TableServiceIntegrationTest extends AbstractUnitTest { public void getCount_notFound_fails() throws SQLException { /* mock */ - MariaDbConfig.createDatabase(CONTAINER_1_PRIVILEGED_DTO, DATABASE_2_INTERNALNAME); + MariaDbConfig.createDatabase(CONTAINER_1_DTO, DATABASE_2_INTERNALNAME); /* test */ assertThrows(QueryMalformedException.class, () -> { - tableService.getCount(TABLE_5_PRIVILEGED_DTO, null); + tableService.getCount(TABLE_5_DTO, null); }); } @@ -701,7 +450,7 @@ public class TableServiceIntegrationTest extends AbstractUnitTest { public void history_succeeds() throws SQLException, TableNotFoundException { /* test */ - final List<TableHistoryDto> response = tableService.history(TABLE_1_PRIVILEGED_DTO, 1000L); + final List<TableHistoryDto> response = tableService.history(TABLE_1_DTO, 1000L); assertEquals(1, response.size()); final TableHistoryDto history0 = response.get(0); assertNotNull(history0.getTimestamp()); @@ -713,11 +462,11 @@ public class TableServiceIntegrationTest extends AbstractUnitTest { public void history_notFound_fails() throws SQLException { /* mock */ - MariaDbConfig.createDatabase(CONTAINER_1_PRIVILEGED_DTO, DATABASE_2_INTERNALNAME); + MariaDbConfig.createDatabase(CONTAINER_1_DTO, DATABASE_2_INTERNALNAME); /* test */ assertThrows(TableNotFoundException.class, () -> { - tableService.history(TABLE_5_PRIVILEGED_DTO, null); + tableService.history(TABLE_5_DTO, null); }); } @@ -739,7 +488,7 @@ public class TableServiceIntegrationTest extends AbstractUnitTest { .build(), RequestBody.fromFile(new File("src/test/resources/csv/weather_aus.csv"))); /* test */ - tableService.importDataset(TABLE_1_PRIVILEGED_DTO, request); + tableService.importDataset(TABLE_1_DTO, request); } @Test @@ -760,7 +509,7 @@ public class TableServiceIntegrationTest extends AbstractUnitTest { /* test */ assertThrows(TableMalformedException.class, () -> { - tableService.importDataset(TABLE_1_PRIVILEGED_DTO, request); + tableService.importDataset(TABLE_1_DTO, request); }); } @@ -782,7 +531,7 @@ public class TableServiceIntegrationTest extends AbstractUnitTest { /* test */ assertThrows(MalformedException.class, () -> { - tableService.importDataset(TABLE_1_PRIVILEGED_DTO, request); + tableService.importDataset(TABLE_1_DTO, request); }); } diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/ViewServiceIntegrationTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/ViewServiceIntegrationTest.java index b5555611c6..658c099724 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/ViewServiceIntegrationTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/service/ViewServiceIntegrationTest.java @@ -1,12 +1,8 @@ package at.tuwien.service; -import at.tuwien.api.database.ViewColumnDto; -import at.tuwien.api.database.ViewDto; import at.tuwien.config.MariaDbConfig; import at.tuwien.config.MariaDbContainerConfig; -import at.tuwien.exception.DatabaseMalformedException; import at.tuwien.exception.ViewMalformedException; -import at.tuwien.exception.ViewNotFoundException; import at.tuwien.test.AbstractUnitTest; import lombok.extern.log4j.Log4j2; import org.junit.jupiter.api.BeforeEach; @@ -20,9 +16,6 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import java.sql.SQLException; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; @Log4j2 @SpringBootTest @@ -40,47 +33,15 @@ public class ViewServiceIntegrationTest extends AbstractUnitTest { public void beforeEach() throws SQLException { genesis(); /* metadata database */ - MariaDbConfig.dropDatabase(CONTAINER_1_PRIVILEGED_DTO, DATABASE_1_INTERNALNAME); - MariaDbConfig.createInitDatabase(CONTAINER_1_PRIVILEGED_DTO, DATABASE_1_DTO); + MariaDbConfig.dropDatabase(CONTAINER_1_DTO, DATABASE_1_INTERNALNAME); + MariaDbConfig.createInitDatabase(CONTAINER_1_DTO, DATABASE_1_DTO); } @Test public void delete_succeeds() throws SQLException, ViewMalformedException { /* test */ - viewService.delete(DATABASE_1_PRIVILEGED_DTO, VIEW_1_INTERNAL_NAME); - } - - @Test - public void create_succeeds() throws SQLException, ViewMalformedException { - - /* test */ - viewService.create(DATABASE_1_PRIVILEGED_DTO, VIEW_1_CREATE_DTO); - } - - @Test - public void getSchemas_succeeds() throws SQLException, ViewNotFoundException, DatabaseMalformedException { - - /* test */ - final List<ViewDto> response = viewService.getSchemas(DATABASE_1_PRIVILEGED_DTO); - final ViewDto view0 = response.get(0); - assertEquals("not_in_metadata_db2", view0.getName()); - assertEquals("not_in_metadata_db2", view0.getInternalName()); - assertEquals(DATABASE_1_ID, view0.getVdbid()); - assertEquals(DATABASE_1_OWNER, view0.getOwner().getId()); - assertFalse(view0.getIsInitialView()); - assertEquals(DATABASE_1_PUBLIC, view0.getIsPublic()); - assertTrue(view0.getQuery().length() >= 69); - assertNotNull(view0.getQueryHash()); - assertEquals(4, view0.getColumns().size()); - final ViewColumnDto column0a = view0.getColumns().get(0); - assertEquals("date", column0a.getInternalName()); - final ViewColumnDto column1a = view0.getColumns().get(1); - assertEquals("location", column1a.getInternalName()); - final ViewColumnDto column2a = view0.getColumns().get(2); - assertEquals("MinTemp", column2a.getInternalName()); - final ViewColumnDto column3a = view0.getColumns().get(3); - assertEquals("Rainfall", column3a.getInternalName()); + viewService.delete(VIEW_1_DTO); } } diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/config/CacheConfig.java b/dbrepo-data-service/services/src/main/java/at/tuwien/config/CacheConfig.java index 45654157d1..c798537b5b 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/config/CacheConfig.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/config/CacheConfig.java @@ -1,11 +1,11 @@ package at.tuwien.config; -import at.tuwien.api.container.internal.PrivilegedContainerDto; +import at.tuwien.api.container.ContainerDto; import at.tuwien.api.database.DatabaseAccessDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; -import at.tuwien.api.database.internal.PrivilegedViewDto; -import at.tuwien.api.database.table.internal.PrivilegedTableDto; -import at.tuwien.api.user.internal.PrivilegedUserDto; +import at.tuwien.api.database.DatabaseDto; +import at.tuwien.api.database.ViewDto; +import at.tuwien.api.database.table.TableDto; +import at.tuwien.api.user.UserDto; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import org.springframework.beans.factory.annotation.Value; @@ -22,13 +22,13 @@ public class CacheConfig { private Long credentialCacheTimeout; @Bean - public Cache<UUID, PrivilegedUserDto> userCache() { - return new ExpiryCache<UUID, PrivilegedUserDto>().build(); + public Cache<UUID, UserDto> userCache() { + return new ExpiryCache<UUID, UserDto>().build(); } @Bean - public Cache<Long, PrivilegedViewDto> viewCache() { - return new ExpiryCache<Long, PrivilegedViewDto>().build(); + public Cache<Long, ViewDto> viewCache() { + return new ExpiryCache<Long, ViewDto>().build(); } @Bean @@ -37,18 +37,18 @@ public class CacheConfig { } @Bean - public Cache<Long, PrivilegedTableDto> tableCache() { - return new ExpiryCache<Long, PrivilegedTableDto>().build(); + public Cache<Long, TableDto> tableCache() { + return new ExpiryCache<Long, TableDto>().build(); } @Bean - public Cache<Long, PrivilegedDatabaseDto> databaseCache() { - return new ExpiryCache<Long, PrivilegedDatabaseDto>().build(); + public Cache<Long, DatabaseDto> databaseCache() { + return new ExpiryCache<Long, DatabaseDto>().build(); } @Bean - public Cache<Long, PrivilegedContainerDto> containerCache() { - return new ExpiryCache<Long, PrivilegedContainerDto>().build(); + public Cache<Long, ContainerDto> containerCache() { + return new ExpiryCache<Long, ContainerDto>().build(); } class ExpiryCache<K, T> { diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/gateway/MetadataServiceGateway.java b/dbrepo-data-service/services/src/main/java/at/tuwien/gateway/MetadataServiceGateway.java index e2851071da..f7fe2f2075 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/gateway/MetadataServiceGateway.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/gateway/MetadataServiceGateway.java @@ -1,13 +1,12 @@ package at.tuwien.gateway; -import at.tuwien.api.container.internal.PrivilegedContainerDto; +import at.tuwien.api.container.ContainerDto; import at.tuwien.api.database.DatabaseAccessDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; -import at.tuwien.api.database.internal.PrivilegedViewDto; -import at.tuwien.api.database.table.internal.PrivilegedTableDto; +import at.tuwien.api.database.DatabaseDto; +import at.tuwien.api.database.ViewDto; +import at.tuwien.api.database.table.TableDto; import at.tuwien.api.identifier.IdentifierBriefDto; import at.tuwien.api.user.UserDto; -import at.tuwien.api.user.internal.PrivilegedUserDto; import at.tuwien.exception.*; import jakarta.validation.constraints.NotNull; @@ -20,12 +19,12 @@ public interface MetadataServiceGateway { * Get a container with given id from the metadata service. * * @param containerId The container id - * @return The container with privileged connection information, if successful. + * @return The container with connection information, if successful. * @throws ContainerNotFoundException The table was not found in the metadata service. * @throws RemoteUnavailableException The remote service is not available. * @throws MetadataServiceException The remote service returned invalid data. */ - PrivilegedContainerDto getContainerById(Long containerId) throws RemoteUnavailableException, + ContainerDto getContainerById(Long containerId) throws RemoteUnavailableException, ContainerNotFoundException, MetadataServiceException; /** @@ -37,7 +36,7 @@ public interface MetadataServiceGateway { * @throws RemoteUnavailableException The remote service is not available. * @throws MetadataServiceException The remote service returned invalid data. */ - PrivilegedDatabaseDto getDatabaseById(Long id) throws DatabaseNotFoundException, RemoteUnavailableException, + DatabaseDto getDatabaseById(Long id) throws DatabaseNotFoundException, RemoteUnavailableException, MetadataServiceException; /** @@ -50,7 +49,7 @@ public interface MetadataServiceGateway { * @throws RemoteUnavailableException The remote service is not available. * @throws MetadataServiceException The remote service returned invalid data. */ - PrivilegedTableDto getTableById(Long databaseId, Long id) throws TableNotFoundException, RemoteUnavailableException, + TableDto getTableById(Long databaseId, Long id) throws TableNotFoundException, RemoteUnavailableException, MetadataServiceException; /** @@ -63,7 +62,7 @@ public interface MetadataServiceGateway { * @throws RemoteUnavailableException The remote service is not available. * @throws MetadataServiceException The remote service returned invalid data. */ - PrivilegedViewDto getViewById(Long databaseId, Long id) throws RemoteUnavailableException, ViewNotFoundException, + ViewDto getViewById(Long databaseId, Long id) throws RemoteUnavailableException, ViewNotFoundException, MetadataServiceException; /** @@ -77,18 +76,6 @@ public interface MetadataServiceGateway { */ UserDto getUserById(UUID userId) throws RemoteUnavailableException, UserNotFoundException, MetadataServiceException; - /** - * Get a user with given user id from the metadata service. - * - * @param userId The user id. - * @return The user, if successful. - * @throws RemoteUnavailableException The remote service is not available and invalid data was returned. - * @throws UserNotFoundException The user was not found in the metadata service. - * @throws MetadataServiceException The remote service returned invalid data. - */ - PrivilegedUserDto getPrivilegedUserById(UUID userId) throws RemoteUnavailableException, UserNotFoundException, - MetadataServiceException; - /** * Get database access for a given user and database id from the metadata service. * diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/gateway/impl/MetadataServiceGatewayImpl.java b/dbrepo-data-service/services/src/main/java/at/tuwien/gateway/impl/MetadataServiceGatewayImpl.java index 7d834992cc..57d6ffab7c 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/gateway/impl/MetadataServiceGatewayImpl.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/gateway/impl/MetadataServiceGatewayImpl.java @@ -1,17 +1,12 @@ package at.tuwien.gateway.impl; import at.tuwien.api.container.ContainerDto; -import at.tuwien.api.container.image.ImageDto; -import at.tuwien.api.container.internal.PrivilegedContainerDto; import at.tuwien.api.database.DatabaseAccessDto; +import at.tuwien.api.database.DatabaseDto; import at.tuwien.api.database.ViewDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; -import at.tuwien.api.database.internal.PrivilegedViewDto; import at.tuwien.api.database.table.TableDto; -import at.tuwien.api.database.table.internal.PrivilegedTableDto; import at.tuwien.api.identifier.IdentifierBriefDto; import at.tuwien.api.user.UserDto; -import at.tuwien.api.user.internal.PrivilegedUserDto; import at.tuwien.config.GatewayConfig; import at.tuwien.exception.*; import at.tuwien.gateway.MetadataServiceGateway; @@ -36,27 +31,24 @@ import java.util.UUID; @Service public class MetadataServiceGatewayImpl implements MetadataServiceGateway { - private final RestTemplate restTemplate; private final RestTemplate internalRestTemplate; private final GatewayConfig gatewayConfig; private final MetadataMapper metadataMapper; @Autowired public MetadataServiceGatewayImpl(@Qualifier("internalRestTemplate") RestTemplate internalRestTemplate, - RestTemplate restTemplate, GatewayConfig gatewayConfig, - MetadataMapper metadataMapper) { - this.restTemplate = restTemplate; + GatewayConfig gatewayConfig, MetadataMapper metadataMapper) { this.internalRestTemplate = internalRestTemplate; this.gatewayConfig = gatewayConfig; this.metadataMapper = metadataMapper; } @Override - public PrivilegedContainerDto getContainerById(Long containerId) throws RemoteUnavailableException, + public ContainerDto getContainerById(Long containerId) throws RemoteUnavailableException, ContainerNotFoundException, MetadataServiceException { final ResponseEntity<ContainerDto> response; final String url = "/api/container/" + containerId; - log.debug("get privileged container info from metadata service: {}", url); + log.debug("get container info from metadata service: {}", url); try { response = internalRestTemplate.exchange(url, HttpMethod.GET, HttpEntity.EMPTY, ContainerDto.class); @@ -73,16 +65,16 @@ public class MetadataServiceGatewayImpl implements MetadataServiceGateway { } final List<String> expectedHeaders = List.of("X-Username", "X-Password"); if (!response.getHeaders().keySet().containsAll(expectedHeaders)) { - log.error("Failed to find all privileged container headers"); + log.error("Failed to find all container headers"); log.debug("expected headers: {}", expectedHeaders); log.debug("found headers: {}", response.getHeaders().keySet()); - throw new MetadataServiceException("Failed to find all privileged container headers"); + throw new MetadataServiceException("Failed to find all container headers"); } if (response.getBody() == null) { log.error("Failed to find container with id {}: body is empty", containerId); throw new MetadataServiceException("Failed to find container with id " + containerId + ": body is empty"); } - final PrivilegedContainerDto container = metadataMapper.containerDtoToPrivilegedContainerDto(response.getBody()); + final ContainerDto container = metadataMapper.containerDtoToContainerDto(response.getBody()); container.setUsername(response.getHeaders().get("X-Username").get(0)); container.setPassword(response.getHeaders().get("X-Password").get(0)); container.setLastRetrieved(Instant.now()); @@ -90,13 +82,13 @@ public class MetadataServiceGatewayImpl implements MetadataServiceGateway { } @Override - public PrivilegedDatabaseDto getDatabaseById(Long id) throws DatabaseNotFoundException, RemoteUnavailableException, + public DatabaseDto getDatabaseById(Long id) throws DatabaseNotFoundException, RemoteUnavailableException, MetadataServiceException { - final ResponseEntity<PrivilegedDatabaseDto> response; + final ResponseEntity<DatabaseDto> response; final String url = "/api/database/" + id; - log.debug("get privileged database info from metadata service: {}", url); + log.debug("get database info from metadata service: {}", url); try { - response = internalRestTemplate.exchange(url, HttpMethod.GET, HttpEntity.EMPTY, PrivilegedDatabaseDto.class); + response = internalRestTemplate.exchange(url, HttpMethod.GET, HttpEntity.EMPTY, DatabaseDto.class); } catch (ResourceAccessException | HttpServerErrorException e) { log.error("Failed to find database with id {}: {}", id, e.getMessage()); throw new RemoteUnavailableException("Failed to find database: " + e.getMessage(), e); @@ -110,16 +102,16 @@ public class MetadataServiceGatewayImpl implements MetadataServiceGateway { } final List<String> expectedHeaders = List.of("X-Username", "X-Password", "X-Host", "X-Port"); if (!response.getHeaders().keySet().containsAll(expectedHeaders)) { - log.error("Failed to find all privileged database headers"); + log.error("Failed to find all database headers"); log.debug("expected headers: {}", expectedHeaders); log.debug("found headers: {}", response.getHeaders().keySet()); - throw new MetadataServiceException("Failed to find all privileged database headers"); + throw new MetadataServiceException("Failed to find all database headers"); } if (response.getBody() == null) { log.error("Failed to find database with id {}: body is empty", id); throw new MetadataServiceException("Failed to find database with id " + id + ": body is empty"); } - final PrivilegedDatabaseDto database = response.getBody(); + final DatabaseDto database = response.getBody(); database.getContainer().setUsername(response.getHeaders().get("X-Username").get(0)); database.getContainer().setPassword(response.getHeaders().get("X-Password").get(0)); database.getContainer().setHost(response.getHeaders().get("X-Host").get(0)); @@ -129,11 +121,11 @@ public class MetadataServiceGatewayImpl implements MetadataServiceGateway { } @Override - public PrivilegedTableDto getTableById(Long databaseId, Long id) throws TableNotFoundException, + public TableDto getTableById(Long databaseId, Long id) throws TableNotFoundException, RemoteUnavailableException, MetadataServiceException { final ResponseEntity<TableDto> response; final String url = "/api/database/" + databaseId + "/table/" + id; - log.debug("get privileged table info from metadata service: {}", url); + log.debug("get table info from metadata service: {}", url); try { response = internalRestTemplate.exchange(url, HttpMethod.GET, HttpEntity.EMPTY, TableDto.class); } catch (ResourceAccessException | HttpServerErrorException e) { @@ -149,33 +141,33 @@ public class MetadataServiceGatewayImpl implements MetadataServiceGateway { } final List<String> expectedHeaders = List.of("X-Type", "X-Host", "X-Port", "X-Username", "X-Password", "X-Database", "X-Table"); if (!response.getHeaders().keySet().containsAll(expectedHeaders)) { - log.error("Failed to find all privileged table headers"); + log.error("Failed to find all table headers"); log.debug("expected headers: {}", expectedHeaders); log.debug("found headers: {}", response.getHeaders().keySet()); - throw new MetadataServiceException("Failed to find all privileged table headers"); + throw new MetadataServiceException("Failed to find all table headers"); } if (response.getBody() == null) { log.error("Failed to find table with id {}: body is empty", id); throw new MetadataServiceException("Failed to find table with id " + id + ": body is empty"); } - final PrivilegedTableDto table = metadataMapper.tableDtoToPrivilegedTableDto(response.getBody()); - table.getDatabase().getContainer().getImage().setJdbcMethod(response.getHeaders().get("X-Type").get(0)); - table.getDatabase().getContainer().setHost(response.getHeaders().get("X-Host").get(0)); - table.getDatabase().getContainer().setPort(Integer.parseInt(response.getHeaders().get("X-Port").get(0))); - table.getDatabase().getContainer().setUsername(response.getHeaders().get("X-Username").get(0)); - table.getDatabase().getContainer().setPassword(response.getHeaders().get("X-Password").get(0)); - table.getDatabase().setInternalName(response.getHeaders().get("X-Database").get(0)); + final TableDto table = metadataMapper.tableDtoToTableDto(response.getBody()); + table.setJdbcMethod(response.getHeaders().get("X-Type").get(0)); + table.setHost(response.getHeaders().get("X-Host").get(0)); + table.setPort(Integer.parseInt(response.getHeaders().get("X-Port").get(0))); + table.setUsername(response.getHeaders().get("X-Username").get(0)); + table.setPassword(response.getHeaders().get("X-Password").get(0)); + table.setDatabase(response.getHeaders().get("X-Database").get(0)); table.setInternalName(response.getHeaders().get("X-Table").get(0)); table.setLastRetrieved(Instant.now()); return table; } @Override - public PrivilegedViewDto getViewById(Long databaseId, Long id) throws RemoteUnavailableException, + public ViewDto getViewById(Long databaseId, Long id) throws RemoteUnavailableException, ViewNotFoundException, MetadataServiceException { final ResponseEntity<ViewDto> response; final String url = "/api/database/" + databaseId + "/view/" + id; - log.debug("get privileged view info from metadata service: {}", url); + log.debug("get view info from metadata service: {}", url); try { response = internalRestTemplate.exchange(url, HttpMethod.GET, HttpEntity.EMPTY, ViewDto.class); } catch (ResourceAccessException | HttpServerErrorException e) { @@ -191,28 +183,22 @@ public class MetadataServiceGatewayImpl implements MetadataServiceGateway { } final List<String> expectedHeaders = List.of("X-Type", "X-Host", "X-Port", "X-Username", "X-Password", "X-Database", "X-View"); if (!response.getHeaders().keySet().containsAll(expectedHeaders)) { - log.error("Failed to find all privileged view headers"); + log.error("Failed to find all view headers"); log.debug("expected headers: {}", expectedHeaders); log.debug("found headers: {}", response.getHeaders().keySet()); - throw new MetadataServiceException("Failed to find all privileged view headers"); + throw new MetadataServiceException("Failed to find all view headers"); } if (response.getBody() == null) { log.error("Failed to find view with id {}: body is empty", id); throw new MetadataServiceException("Failed to find view with id " + id + ": body is empty"); } - final PrivilegedViewDto view = metadataMapper.viewDtoToPrivilegedViewDto(response.getBody()); - view.setDatabase(PrivilegedDatabaseDto.builder() - .internalName(response.getHeaders().get("X-Database").get(0)) - .container(PrivilegedContainerDto.builder() - .host(response.getHeaders().get("X-Host").get(0)) - .port(Integer.parseInt(response.getHeaders().get("X-Port").get(0))) - .username(response.getHeaders().get("X-Username").get(0)) - .password(response.getHeaders().get("X-Password").get(0)) - .image(ImageDto.builder() - .jdbcMethod(response.getHeaders().get("X-Type").get(0)) - .build()) - .build()) - .build()); + final ViewDto view = metadataMapper.viewDtoToViewDto(response.getBody()); + view.setJdbcMethod(response.getHeaders().get("X-Type").get(0)); + view.setHost(response.getHeaders().get("X-Host").get(0)); + view.setPort(Integer.parseInt(response.getHeaders().get("X-Port").get(0))); + view.setUsername(response.getHeaders().get("X-Username").get(0)); + view.setPassword(response.getHeaders().get("X-Password").get(0)); + view.setDatabase(response.getHeaders().get("X-Database").get(0)); view.setInternalName(response.getHeaders().get("X-View").get(0)); view.setLastRetrieved(Instant.now()); return view; @@ -223,33 +209,7 @@ public class MetadataServiceGatewayImpl implements MetadataServiceGateway { MetadataServiceException { final ResponseEntity<UserDto> response; final String url = "/api/user/" + userId; - log.debug("get user info from metadata service: {}", url); - try { - response = internalRestTemplate.exchange(url, HttpMethod.GET, HttpEntity.EMPTY, UserDto.class); - } catch (ResourceAccessException | HttpServerErrorException e) { - log.error("Failed to find user with id {}: {}", userId, e.getMessage()); - throw new RemoteUnavailableException("Failed to find user: " + e.getMessage(), e); - } catch (HttpClientErrorException.NotFound e) { - log.error("Failed to find user with id {}: not found: {}", userId, e.getMessage()); - throw new UserNotFoundException("Failed to find user: " + e.getMessage(), e); - } - if (!response.getStatusCode().equals(HttpStatus.OK)) { - log.error("Failed to find user with id {}: service responded unsuccessful: {}", userId, response.getStatusCode()); - throw new MetadataServiceException("Failed to find user: service responded unsuccessful: " + response.getStatusCode()); - } - if (response.getBody() == null) { - log.error("Failed to find user with id {}: body is empty", userId); - throw new MetadataServiceException("Failed to find user with id " + userId + ": body is empty"); - } - return response.getBody(); - } - - @Override - public PrivilegedUserDto getPrivilegedUserById(UUID userId) throws RemoteUnavailableException, UserNotFoundException, - MetadataServiceException { - final ResponseEntity<UserDto> response; - final String url = "/api/user/" + userId; - log.debug("get privileged user info from metadata service: {}", url); + log.debug("get user info from metadata service: {}", url); try { response = internalRestTemplate.exchange(url, HttpMethod.GET, HttpEntity.EMPTY, UserDto.class); } catch (ResourceAccessException | HttpServerErrorException e) { @@ -265,16 +225,16 @@ public class MetadataServiceGatewayImpl implements MetadataServiceGateway { } final List<String> expectedHeaders = List.of("X-Username", "X-Password"); if (!response.getHeaders().keySet().containsAll(expectedHeaders)) { - log.error("Failed to find all privileged user headers"); + log.error("Failed to find all user headers"); log.debug("expected headers: {}", expectedHeaders); log.debug("found headers: {}", response.getHeaders().keySet()); - throw new MetadataServiceException("Failed to find all privileged user headers"); + throw new MetadataServiceException("Failed to find all user headers"); } if (response.getBody() == null) { log.error("Failed to find user with id {}: body is empty", userId); throw new MetadataServiceException("Failed to find user with id " + userId + ": body is empty"); } - final PrivilegedUserDto user = metadataMapper.userDtoToPrivilegedUserDto(response.getBody()); + final UserDto user = metadataMapper.userDtoToUserDto(response.getBody()); user.setUsername(response.getHeaders().get("X-Username").get(0)); user.setPassword(response.getHeaders().get("X-Password").get(0)); user.setLastRetrieved(Instant.now()); diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/listener/DefaultListener.java b/dbrepo-data-service/services/src/main/java/at/tuwien/listener/DefaultListener.java index fac47a3d80..e31455a75e 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/listener/DefaultListener.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/listener/DefaultListener.java @@ -1,7 +1,9 @@ package at.tuwien.listener; -import at.tuwien.api.database.table.internal.PrivilegedTableDto; -import at.tuwien.exception.*; +import at.tuwien.api.database.table.TableDto; +import at.tuwien.exception.MetadataServiceException; +import at.tuwien.exception.RemoteUnavailableException; +import at.tuwien.exception.TableNotFoundException; import at.tuwien.gateway.MetadataServiceGateway; import at.tuwien.service.QueueService; import com.fasterxml.jackson.core.type.TypeReference; @@ -57,7 +59,7 @@ public class DefaultListener implements MessageListener { log.trace("received message for table with id {} of database id {}: {} bytes", tableId, databaseId, message.getMessageProperties().getContentLength()); final Map<String, Object> body; try { - final PrivilegedTableDto table = metadataServiceGateway.getTableById(databaseId, tableId); + final TableDto table = metadataServiceGateway.getTableById(databaseId, tableId); body = objectMapper.readValue(message.getBody(), typeRef); queueService.insert(table, body); } catch (IOException e) { diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/MariaDbMapper.java b/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/MariaDbMapper.java index db848cab7e..7f5b63b21c 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/MariaDbMapper.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/MariaDbMapper.java @@ -1,12 +1,12 @@ package at.tuwien.mapper; +import at.tuwien.api.database.table.TableDto; import at.tuwien.api.database.table.TupleDeleteDto; import at.tuwien.api.database.table.TupleDto; import at.tuwien.api.database.table.TupleUpdateDto; import at.tuwien.api.database.table.columns.ColumnCreateDto; import at.tuwien.api.database.table.columns.ColumnDto; import at.tuwien.api.database.table.columns.ColumnTypeDto; -import at.tuwien.api.database.table.internal.PrivilegedTableDto; import at.tuwien.exception.QueryMalformedException; import at.tuwien.exception.TableMalformedException; import at.tuwien.utils.MariaDbUtil; @@ -519,7 +519,7 @@ public interface MariaDbMapper { return statement.toString(); } - default String tupleToRawDeleteQuery(PrivilegedTableDto table, TupleDeleteDto data) throws TableMalformedException { + default String tupleToRawDeleteQuery(TableDto table, TupleDeleteDto data) throws TableMalformedException { log.trace("table csv to delete query, table.id={}, data.keys={}", table.getId(), data.getKeys()); if (table.getColumns().isEmpty()) { throw new TableMalformedException("Columns are not known"); @@ -540,14 +540,14 @@ public interface MariaDbMapper { return statement.toString(); } - default String tupleToRawUpdateQuery(PrivilegedTableDto table, TupleUpdateDto data) + default String tupleToRawUpdateQuery(TableDto table, TupleUpdateDto data) throws TableMalformedException { if (table.getColumns().isEmpty()) { throw new TableMalformedException("Columns are not known"); } /* parameterized query for prepared statement */ final StringBuilder statement = new StringBuilder("UPDATE `") - .append(table.getDatabase().getInternalName()) + .append(table.getDatabase()) .append("`.`") .append(table.getInternalName()) .append("` SET "); @@ -579,13 +579,13 @@ public interface MariaDbMapper { return statement.toString(); } - default String tupleToRawCreateQuery(PrivilegedTableDto table, TupleDto data) throws TableMalformedException { + default String tupleToRawCreateQuery(TableDto table, TupleDto data) throws TableMalformedException { if (table.getColumns().isEmpty()) { throw new TableMalformedException("Columns are not known"); } /* parameterized query for prepared statement */ final StringBuilder statement = new StringBuilder("INSERT INTO `") - .append(table.getDatabase().getInternalName()) + .append(table.getDatabase()) .append("`.`") .append(table.getInternalName()) .append("` ("); diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/MetadataMapper.java b/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/MetadataMapper.java index 0adfafa8f9..359e251ea2 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/MetadataMapper.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/MetadataMapper.java @@ -2,28 +2,23 @@ package at.tuwien.mapper; import at.tuwien.api.container.ContainerDto; import at.tuwien.api.container.image.ImageDto; -import at.tuwien.api.container.internal.PrivilegedContainerDto; import at.tuwien.api.database.DatabaseBriefDto; import at.tuwien.api.database.DatabaseDto; import at.tuwien.api.database.ViewColumnDto; import at.tuwien.api.database.ViewDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; -import at.tuwien.api.database.internal.PrivilegedViewDto; import at.tuwien.api.database.query.QueryDto; import at.tuwien.api.database.table.TableBriefDto; import at.tuwien.api.database.table.TableDto; import at.tuwien.api.database.table.columns.ColumnDto; -import at.tuwien.api.database.table.internal.PrivilegedTableDto; import at.tuwien.api.identifier.IdentifierBriefDto; import at.tuwien.api.identifier.IdentifierDto; import at.tuwien.api.user.UserBriefDto; import at.tuwien.api.user.UserDto; -import at.tuwien.api.user.internal.PrivilegedUserDto; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; -@Mapper(componentModel = "spring", imports = {PrivilegedDatabaseDto.class, PrivilegedContainerDto.class, ImageDto.class}) +@Mapper(componentModel = "spring", imports = {DatabaseDto.class, ContainerDto.class, ImageDto.class}) public interface MetadataMapper { org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MetadataMapper.class); @@ -32,28 +27,21 @@ public interface MetadataMapper { return subset.getQueryHash(); } - PrivilegedContainerDto containerDtoToPrivilegedContainerDto(ContainerDto data); + ContainerDto containerDtoToContainerDto(ContainerDto data); - DatabaseDto privilegedDatabaseDtoToDatabaseDto(PrivilegedDatabaseDto data); - - DatabaseBriefDto privilegedDatabaseDtoToDatabaseBriefDto(PrivilegedDatabaseDto data); - - TableDto privilegedTableDtoToTableDto(PrivilegedTableDto data); + DatabaseBriefDto databaseDtoToDatabaseBriefDto(DatabaseDto data); ColumnDto viewColumnDtoToColumnDto(ViewColumnDto data); ViewColumnDto columnDtoToViewColumnDto(ColumnDto data); - @Mappings({ - @Mapping(target = "database", expression = "java(PrivilegedDatabaseDto.builder().container(PrivilegedContainerDto.builder().image(new ImageDto()).build()).build())") - }) - PrivilegedTableDto tableDtoToPrivilegedTableDto(TableDto data); + TableDto tableDtoToTableDto(TableDto data); - PrivilegedViewDto viewDtoToPrivilegedViewDto(ViewDto data); + ViewDto viewDtoToViewDto(ViewDto data); - ContainerDto privilegedContainerDtoToContainerDto(PrivilegedContainerDto data); + ContainerDto ContainerDtoToContainerDto(ContainerDto data); - PrivilegedUserDto userDtoToPrivilegedUserDto(UserDto data); + UserDto userDtoToUserDto(UserDto data); UserBriefDto userDtoToUserBriefDto(UserDto data); @@ -64,6 +52,8 @@ public interface MetadataMapper { IdentifierBriefDto identifierDtoToIdentifierBriefDto(IdentifierDto data); + TableDto databaseDtoToTableDto(DatabaseDto data); + default String metricToUri(String baseUrl, Long databaseId, Long tableId, Long subsetId, Long viewId) { final StringBuilder uri = new StringBuilder(baseUrl) .append("/database/") diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/AccessService.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/AccessService.java index 89707ce8f2..c42fc28101 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/service/AccessService.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/service/AccessService.java @@ -1,8 +1,8 @@ package at.tuwien.service; import at.tuwien.api.database.AccessTypeDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; -import at.tuwien.api.user.internal.PrivilegedUserDto; +import at.tuwien.api.database.DatabaseDto; +import at.tuwien.api.user.UserDto; import at.tuwien.exception.DatabaseMalformedException; import java.sql.SQLException; @@ -18,7 +18,7 @@ public interface AccessService { * @throws SQLException The connection to the database could not be established. * @throws DatabaseMalformedException The database schema is malformed. */ - void create(PrivilegedDatabaseDto database, PrivilegedUserDto user, AccessTypeDto access) throws SQLException, + void create(DatabaseDto database, UserDto user, AccessTypeDto access) throws SQLException, DatabaseMalformedException; /** @@ -30,7 +30,7 @@ public interface AccessService { * @throws SQLException The connection to the database could not be established. * @throws DatabaseMalformedException The database schema is malformed. */ - void update(PrivilegedDatabaseDto database, PrivilegedUserDto user, AccessTypeDto access) throws SQLException, + void update(DatabaseDto database, UserDto user, AccessTypeDto access) throws SQLException, DatabaseMalformedException; /** @@ -41,6 +41,5 @@ public interface AccessService { * @throws SQLException The connection to the database could not be established. * @throws DatabaseMalformedException The database schema is malformed. */ - void delete(PrivilegedDatabaseDto database, PrivilegedUserDto user) throws SQLException, - DatabaseMalformedException; + void delete(DatabaseDto database, UserDto user) throws SQLException, DatabaseMalformedException; } diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/ContainerService.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/ContainerService.java new file mode 100644 index 0000000000..4f9e92ed78 --- /dev/null +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/service/ContainerService.java @@ -0,0 +1,33 @@ +package at.tuwien.service; + +import at.tuwien.api.container.ContainerDto; +import at.tuwien.api.database.DatabaseDto; +import at.tuwien.api.database.internal.CreateDatabaseDto; +import at.tuwien.exception.DatabaseMalformedException; +import at.tuwien.exception.QueryStoreCreateException; + +import java.sql.SQLException; + +public interface ContainerService { + + /** + * Creates a database in the given container. + * @param container The container. + * @param data The database metadata. + * @return The created database, if successful. + * @throws SQLException The connection to the database could not be established. + * @throws DatabaseMalformedException The database schema is malformed. + */ + DatabaseDto createDatabase(ContainerDto container, CreateDatabaseDto data) throws SQLException, + DatabaseMalformedException; + + /** + * Creates the query store in the container and database. + * + * @param container The container. + * @param databaseName The database name. + * @throws SQLException The connection to the database could not be established. + * @throws QueryStoreCreateException The query store could not be created. + */ + void createQueryStore(ContainerDto container, String databaseName) throws SQLException, QueryStoreCreateException; +} diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/CredentialService.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/CredentialService.java index ff09d6672f..fe3df343db 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/service/CredentialService.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/service/CredentialService.java @@ -1,11 +1,11 @@ package at.tuwien.service; -import at.tuwien.api.container.internal.PrivilegedContainerDto; +import at.tuwien.api.container.ContainerDto; import at.tuwien.api.database.DatabaseAccessDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; -import at.tuwien.api.database.internal.PrivilegedViewDto; -import at.tuwien.api.database.table.internal.PrivilegedTableDto; -import at.tuwien.api.user.internal.PrivilegedUserDto; +import at.tuwien.api.database.DatabaseDto; +import at.tuwien.api.database.ViewDto; +import at.tuwien.api.database.table.TableDto; +import at.tuwien.api.user.UserDto; import at.tuwien.exception.*; import java.util.UUID; @@ -22,7 +22,7 @@ public interface CredentialService { * @throws RemoteUnavailableException The remote service is not available. * @throws MetadataServiceException The remote service returned invalid data. */ - PrivilegedDatabaseDto getDatabase(Long id) throws DatabaseNotFoundException, RemoteUnavailableException, + DatabaseDto getDatabase(Long id) throws DatabaseNotFoundException, RemoteUnavailableException, MetadataServiceException; /** @@ -35,7 +35,7 @@ public interface CredentialService { * @throws RemoteUnavailableException The remote service is not available. * @throws MetadataServiceException The remote service returned invalid data. */ - PrivilegedContainerDto getContainer(Long id) throws ContainerNotFoundException, RemoteUnavailableException, + ContainerDto getContainer(Long id) throws ContainerNotFoundException, RemoteUnavailableException, MetadataServiceException; /** @@ -49,7 +49,7 @@ public interface CredentialService { * @throws RemoteUnavailableException The remote service is not available. * @throws MetadataServiceException The remote service returned invalid data. */ - PrivilegedTableDto getTable(Long databaseId, Long tableId) throws RemoteUnavailableException, + TableDto getTable(Long databaseId, Long tableId) throws RemoteUnavailableException, MetadataServiceException, TableNotFoundException; /** @@ -63,7 +63,7 @@ public interface CredentialService { * @throws RemoteUnavailableException The remote service is not available. * @throws MetadataServiceException The remote service returned invalid data. */ - PrivilegedViewDto getView(Long databaseId, Long viewId) throws RemoteUnavailableException, + ViewDto getView(Long databaseId, Long viewId) throws RemoteUnavailableException, MetadataServiceException, ViewNotFoundException; /** @@ -76,7 +76,7 @@ public interface CredentialService { * @throws RemoteUnavailableException The remote service is not available. * @throws MetadataServiceException The remote service returned invalid data. */ - PrivilegedUserDto getUser(UUID id) throws RemoteUnavailableException, MetadataServiceException, + UserDto getUser(UUID id) throws RemoteUnavailableException, MetadataServiceException, UserNotFoundException; /** diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/DatabaseService.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/DatabaseService.java index 271b2abb82..5a120f44d2 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/service/DatabaseService.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/service/DatabaseService.java @@ -1,27 +1,92 @@ package at.tuwien.service; -import at.tuwien.api.container.internal.PrivilegedContainerDto; import at.tuwien.api.database.DatabaseDto; -import at.tuwien.api.database.internal.CreateDatabaseDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; +import at.tuwien.api.database.ViewCreateDto; +import at.tuwien.api.database.ViewDto; +import at.tuwien.api.database.table.TableDto; +import at.tuwien.api.database.table.internal.TableCreateDto; import at.tuwien.api.user.internal.UpdateUserPasswordDto; import at.tuwien.exception.*; import java.sql.SQLException; +import java.util.List; public interface DatabaseService { /** - * Creates a database in the given container. - * @param container The container. - * @param data The database metadata. - * @return The created database, if successful. + * Inspects the schema (columns with names, data types) of a view with given name in the given database. + * @param database The database. + * @param viewName The view name. + * @return The inspected view if successful. * @throws SQLException The connection to the database could not be established. - * @throws DatabaseMalformedException The database schema is malformed. + * @throws ViewNotFoundException The view was not found in the given database. + */ + ViewDto inspectView(DatabaseDto database, String viewName) throws SQLException, ViewNotFoundException; + + /** + * Creates a table in given data database with table definition. + * + * @param database The data database object. + * @param data The table definition. + * @return The generated table. + * @throws SQLException Query statement is malformed. + * @throws TableMalformedException The table schema is malformed. + * @throws TableExistsException The table name already exists in the information_schema. + * @throws TableNotFoundException The table could not be inspected in the metadata database. */ - PrivilegedDatabaseDto create(PrivilegedContainerDto container, CreateDatabaseDto data) throws SQLException, + TableDto createTable(DatabaseDto database, TableCreateDto data) throws SQLException, + TableMalformedException, TableExistsException, TableNotFoundException; + + Boolean existsView(DatabaseDto database, String viewName) throws SQLException, + QueryMalformedException; + + /** + * Creates a view in given data database with view definition. + * @param database The data database object. + * @param data The view definition. + * @return The generated view. + * @throws SQLException + * @throws ViewMalformedException + */ + ViewDto createView(DatabaseDto database, ViewCreateDto data) throws SQLException, + ViewMalformedException; + + /** + * Gets the metadata schema for a given database. + * + * @param database The database. + * @return The list of view metadata. + * @throws SQLException The connection to the data database was unsuccessful. + * @throws DatabaseMalformedException The columns that are referenced in the views are unknown to the Metadata Database. Call {@link TableService#getSchemas(DatabaseDto)} beforehand. + * @throws ViewNotFoundException The view with given name was not found. + */ + List<ViewDto> exploreViews(DatabaseDto database) throws SQLException, DatabaseMalformedException, + ViewNotFoundException; + + /** + * Get table schemas from the information_schema in the data database. + * + * @param database The data database object. + * @return List of tables, if successful. + * @throws SQLException Failed to parse SQL query, contains invalid syntax. + * @throws TableNotFoundException The table could not be inspected in the data database. + * @throws DatabaseMalformedException The database inspection was unsuccessful, likely due to a bug in the mapping. + */ + List<TableDto> exploreTables(DatabaseDto database) throws SQLException, TableNotFoundException, DatabaseMalformedException; + /** + * Inspects the schema (columns with names, data types, unique-, check-, primary- and foreign key constraints) of + * a table with given name in the given database. + * + * @param database The database. + * @param tableName The table name. + * @return The inspected table if successful. + * @throws SQLException The connection to the database could not be established. + * @throws TableNotFoundException The table was not found in the given database. + */ + TableDto inspectTable(DatabaseDto database, String tableName) throws SQLException, TableNotFoundException; + /** * Updates a user's password in a given database. * @param database The database. @@ -29,6 +94,6 @@ public interface DatabaseService { * @throws SQLException The connection to the database could not be established. * @throws DatabaseMalformedException The database schema is malformed. */ - void update(PrivilegedDatabaseDto database, UpdateUserPasswordDto data) throws SQLException, + void update(DatabaseDto database, UpdateUserPasswordDto data) throws SQLException, DatabaseMalformedException; } diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/QueueService.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/QueueService.java index 79a23932b5..6a03f5d767 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/service/QueueService.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/service/QueueService.java @@ -1,6 +1,6 @@ package at.tuwien.service; -import at.tuwien.api.database.table.internal.PrivilegedTableDto; +import at.tuwien.api.database.table.TableDto; import java.sql.SQLException; import java.util.Map; @@ -14,5 +14,5 @@ public interface QueueService { * @param data The data. * @throws SQLException The connection to the database could not be established. */ - void insert(PrivilegedTableDto table, Map<String, Object> data) throws SQLException; + void insert(TableDto table, Map<String, Object> data) throws SQLException; } diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/SchemaService.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/SchemaService.java deleted file mode 100644 index f5ef05b44a..0000000000 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/service/SchemaService.java +++ /dev/null @@ -1,33 +0,0 @@ -package at.tuwien.service; - -import at.tuwien.api.database.ViewDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; -import at.tuwien.api.database.table.TableDto; -import at.tuwien.exception.*; - -import java.sql.SQLException; - -public interface SchemaService { - - /** - * Inspects the schema (columns with names, data types, unique-, check-, primary- and foreign key constraints) of - * a table with given name in the given database. - * @param database The database. - * @param tableName The table name. - * @return The inspected table if successful. - * @throws SQLException The connection to the database could not be established. - * @throws TableNotFoundException The table was not found in the given database. - */ - TableDto inspectTable(PrivilegedDatabaseDto database, String tableName) throws SQLException, - TableNotFoundException; - - /** - * Inspects the schema (columns with names, data types) of a view with given name in the given database. - * @param database The database. - * @param viewName The table name. - * @return The inspected view if successful. - * @throws SQLException The connection to the database could not be established. - * @throws ViewNotFoundException The view was not found in the given database. - */ - ViewDto inspectView(PrivilegedDatabaseDto database, String viewName) throws SQLException, ViewNotFoundException; -} diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/SubsetService.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/SubsetService.java index 4a3455fbc4..b2de5cecca 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/service/SubsetService.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/service/SubsetService.java @@ -1,7 +1,6 @@ package at.tuwien.service; -import at.tuwien.api.container.internal.PrivilegedContainerDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; +import at.tuwien.api.database.DatabaseDto; import at.tuwien.api.database.query.QueryDto; import at.tuwien.exception.*; import org.apache.spark.sql.Dataset; @@ -14,17 +13,6 @@ import java.util.UUID; public interface SubsetService { - /** - * Creates the query store in the container and database. - * - * @param container The container. - * @param databaseName The database name. - * @throws SQLException The connection to the database could not be established. - * @throws QueryStoreCreateException The query store could not be created. - */ - void createQueryStore(PrivilegedContainerDto container, String databaseName) throws SQLException, - QueryStoreCreateException; - /** * Retrieve data from a subset in a database and optionally paginate with number of page and size of results. * @@ -38,7 +26,7 @@ public interface SubsetService { * @throws QueryMalformedException The mapped query produced a database error. * @throws TableNotFoundException The database table is malformed. */ - Dataset<Row> getData(PrivilegedDatabaseDto database, QueryDto subset, Long page, Long size) + Dataset<Row> getData(DatabaseDto database, QueryDto subset, Long page, Long size) throws ViewMalformedException, SQLException, QueryMalformedException, TableNotFoundException; /** @@ -52,7 +40,7 @@ public interface SubsetService { * @throws QueryStoreInsertException The query store refused to insert the query. * @throws SQLException The connection to the database could not be established. */ - Long create(PrivilegedDatabaseDto database, String statement, Instant timestamp, UUID userId) + Long create(DatabaseDto database, String statement, Instant timestamp, UUID userId) throws QueryStoreInsertException, SQLException; /** @@ -64,7 +52,7 @@ public interface SubsetService { * @throws TableMalformedException The table is malformed. * @throws SQLException The connection to the database could not be established. */ - Long reExecuteCount(PrivilegedDatabaseDto database, QueryDto query) throws TableMalformedException, + Long reExecuteCount(DatabaseDto database, QueryDto query) throws TableMalformedException, SQLException, QueryMalformedException; /** @@ -75,11 +63,11 @@ public interface SubsetService { * @return The list of queries. * @throws SQLException The connection to the database could not be established. * @throws QueryNotFoundException The query was not found for re-execution. - * @throws RemoteUnavailableException The privileged database information could not be found in the Metadata Service. + * @throws RemoteUnavailableException The database information could not be found in the Metadata Service. * @throws DatabaseNotFoundException The database was not found in the Metadata Service. * @throws MetadataServiceException The Metadata Service responded unexpected. */ - List<QueryDto> findAll(PrivilegedDatabaseDto database, Boolean filterPersisted) throws SQLException, + List<QueryDto> findAll(DatabaseDto database, Boolean filterPersisted) throws SQLException, QueryNotFoundException, RemoteUnavailableException, DatabaseNotFoundException, MetadataServiceException; /** @@ -93,7 +81,7 @@ public interface SubsetService { * @throws QueryMalformedException The mapped query produced a database error. * @throws TableMalformedException The database table is malformed. */ - Long executeCountNonPersistent(PrivilegedDatabaseDto database, String statement, Instant timestamp) + Long executeCountNonPersistent(DatabaseDto database, String statement, Instant timestamp) throws SQLException, QueryMalformedException, TableMalformedException; /** @@ -104,12 +92,12 @@ public interface SubsetService { * @return The query. * @throws QueryNotFoundException The query store did not return a query. * @throws SQLException The connection to the database could not be established. - * @throws RemoteUnavailableException The privileged database information could not be found in the Metadata Service. + * @throws RemoteUnavailableException The database information could not be found in the Metadata Service. * @throws UserNotFoundException The user that created the query was not found in the Metadata Service. * @throws DatabaseNotFoundException The database metadata was not found in the Metadata Service. * @throws MetadataServiceException Communication with the Metadata Service failed. */ - QueryDto findById(PrivilegedDatabaseDto database, Long queryId) throws QueryNotFoundException, SQLException, + QueryDto findById(DatabaseDto database, Long queryId) throws QueryNotFoundException, SQLException, RemoteUnavailableException, UserNotFoundException, DatabaseNotFoundException, MetadataServiceException; /** @@ -122,7 +110,7 @@ public interface SubsetService { * @throws SQLException The connection to the database could not be established. * @throws QueryStoreInsertException The query store failed to insert the query. */ - Long storeQuery(PrivilegedDatabaseDto database, String query, Instant timestamp, UUID userId) throws SQLException, + Long storeQuery(DatabaseDto database, String query, Instant timestamp, UUID userId) throws SQLException, QueryStoreInsertException; /** @@ -134,7 +122,7 @@ public interface SubsetService { * @throws SQLException The connection to the database could not be established. * @throws QueryStorePersistException The query store failed to persist/unpersist the query. */ - void persist(PrivilegedDatabaseDto database, Long queryId, Boolean persist) throws SQLException, + void persist(DatabaseDto database, Long queryId, Boolean persist) throws SQLException, QueryStorePersistException; /** @@ -144,5 +132,5 @@ public interface SubsetService { * @throws SQLException The connection to the database could not be established. * @throws QueryStoreGCException The query store failed to delete stale queries. */ - void deleteStaleQueries(PrivilegedDatabaseDto database) throws SQLException, QueryStoreGCException; + void deleteStaleQueries(DatabaseDto database) throws SQLException, QueryStoreGCException; } diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/TableService.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/TableService.java index 0564639874..b74c491abe 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/service/TableService.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/service/TableService.java @@ -1,11 +1,9 @@ package at.tuwien.service; import at.tuwien.api.SortTypeDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; +import at.tuwien.api.database.DatabaseDto; import at.tuwien.api.database.query.ImportDto; import at.tuwien.api.database.table.*; -import at.tuwien.api.database.table.internal.PrivilegedTableDto; -import at.tuwien.api.database.table.internal.TableCreateDto; import at.tuwien.exception.*; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; @@ -16,57 +14,19 @@ import java.util.List; public interface TableService { - /** - * Get table schemas from the information_schema in the data database. - * - * @param database The data database privileged object. - * @return List of tables, if successful. - * @throws SQLException Failed to parse SQL query, contains invalid syntax. - * @throws TableNotFoundException The table could not be inspected in the data database. - * @throws DatabaseMalformedException The database inspection was unsuccessful, likely due to a bug in the mapping. - */ - List<TableDto> getSchemas(PrivilegedDatabaseDto database) throws SQLException, TableNotFoundException, - DatabaseMalformedException; - /** * Generate table statistic for a given table. Only numerical columns are calculated. * + * @param database The database. * @param table The table. * @return The table statistic, if successful. * @throws SQLException Failed to parse SQL query, contains invalid syntax. * @throws TableMalformedException The table statistic generation was unsuccessful, likely due to a bug in the mapping. * @throws TableNotFoundException The table could not be inspected in the data database. */ - TableStatisticDto getStatistics(PrivilegedTableDto table) throws SQLException, TableMalformedException, + TableStatisticDto getStatistics(DatabaseDto database, TableDto table) throws SQLException, TableMalformedException, TableNotFoundException; - /** - * Finds a table with given data database and table name. - * - * @param database The data database. - * @param tableName The table name. - * @return The table, if successful. - * @throws TableNotFoundException The table could not be inspected in the data database. - * @throws SQLException Failed to parse SQL query, contains invalid syntax. - * @throws QueryMalformedException The inspection query is malformed. - */ - TableDto find(PrivilegedDatabaseDto database, String tableName) throws TableNotFoundException, SQLException, - QueryMalformedException; - - /** - * Creates a table in given data database with table definition. - * - * @param database The data database privileged object. - * @param data The table definition. - * @return The created table, if successful. - * @throws SQLException Query statement is malformed. - * @throws TableMalformedException The table schema is malformed. - * @throws TableExistsException The table name already exists in the information_schema. - * @throws TableNotFoundException The table could not be inspected in the metadata database. - */ - TableDto createTable(PrivilegedDatabaseDto database, TableCreateDto data) throws SQLException, - TableMalformedException, TableExistsException, TableNotFoundException; - /** * Updating table description. * @@ -76,7 +36,7 @@ public interface TableService { * @throws TableMalformedException The table schema is malformed. * @throws TableNotFoundException The table could not be inspected in the metadata database. */ - void updateTable(PrivilegedTableDto table, TableUpdateDto data) throws SQLException, + void updateTable(TableDto table, TableUpdateDto data) throws SQLException, TableMalformedException, TableNotFoundException; /** @@ -86,7 +46,7 @@ public interface TableService { * @throws SQLException Failed to parse SQL query, contains invalid syntax. * @throws QueryMalformedException The drop table query is malformed. */ - void delete(PrivilegedTableDto table) throws SQLException, QueryMalformedException; + void delete(TableDto table) throws SQLException, QueryMalformedException; /** * Obtains the table history for a given table object. @@ -97,7 +57,7 @@ public interface TableService { * @throws SQLException Failed to parse SQL query, contains invalid syntax. * @throws TableNotFoundException The table could not be found in the data database. */ - List<TableHistoryDto> history(PrivilegedTableDto table, Long size) throws SQLException, TableNotFoundException; + List<TableHistoryDto> history(TableDto table, Long size) throws SQLException, TableNotFoundException; /** * Obtains the table data tuples count at time. @@ -108,7 +68,7 @@ public interface TableService { * @throws SQLException Failed to parse SQL query, contains invalid syntax. * @throws QueryMalformedException The count query is malformed, likely due to a bug in the application. */ - Long getCount(PrivilegedTableDto table, Instant timestamp) throws SQLException, + Long getCount(TableDto table, Instant timestamp) throws SQLException, QueryMalformedException; /** @@ -123,7 +83,7 @@ public interface TableService { * @throws SQLException Failed to parse SQL query, contains invalid syntax. * @throws QueryMalformedException The import query is malformed, likely due to a bug in the application. */ - void importDataset(PrivilegedTableDto table, ImportDto data) throws MalformedException, StorageNotFoundException, + void importDataset(TableDto table, ImportDto data) throws MalformedException, StorageNotFoundException, StorageUnavailableException, SQLException, QueryMalformedException, TableMalformedException; /** @@ -135,7 +95,7 @@ public interface TableService { * @throws TableMalformedException The tuple is malformed and does not fit the table schema. * @throws QueryMalformedException The delete query is malformed, likely due to a bug in the application. */ - void deleteTuple(PrivilegedTableDto table, TupleDeleteDto data) throws SQLException, + void deleteTuple(TableDto table, TupleDeleteDto data) throws SQLException, TableMalformedException, QueryMalformedException; /** @@ -149,7 +109,7 @@ public interface TableService { * @throws StorageUnavailableException Failed to establish a connection with the Storage Service. * @throws StorageNotFoundException The storage service was not able to find the dataset for import. */ - void createTuple(PrivilegedTableDto table, TupleDto data) throws SQLException, + void createTuple(TableDto table, TupleDto data) throws SQLException, QueryMalformedException, TableMalformedException, StorageUnavailableException, StorageNotFoundException; /** @@ -161,10 +121,10 @@ public interface TableService { * @throws QueryMalformedException The update query is malformed, likely due to a bug in the application. * @throws TableMalformedException The tuple is malformed and does not fit the table schema. */ - void updateTuple(PrivilegedTableDto table, TupleUpdateDto data) throws SQLException, + void updateTuple(TableDto table, TupleUpdateDto data) throws SQLException, QueryMalformedException, TableMalformedException; - Dataset<Row> getData(PrivilegedDatabaseDto database, String tableOrView, Instant timestamp, + Dataset<Row> getData(DatabaseDto database, String tableOrView, Instant timestamp, Long page, Long size, SortTypeDto sortDirection, String sortColumn) throws QueryMalformedException, TableNotFoundException; } diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/ViewService.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/ViewService.java index ec7a723261..8f721f9974 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/service/ViewService.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/service/ViewService.java @@ -1,66 +1,22 @@ package at.tuwien.service; -import at.tuwien.api.database.ViewCreateDto; import at.tuwien.api.database.ViewDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; -import at.tuwien.api.database.internal.PrivilegedViewDto; -import at.tuwien.api.database.query.QueryDto; -import at.tuwien.exception.DatabaseMalformedException; import at.tuwien.exception.QueryMalformedException; import at.tuwien.exception.ViewMalformedException; -import at.tuwien.exception.ViewNotFoundException; import java.sql.SQLException; import java.time.Instant; -import java.util.List; public interface ViewService { - Boolean existsByName(PrivilegedDatabaseDto database, String name) throws SQLException, - QueryMalformedException; - - /** - * Gets the metadata schema for a given database. - * - * @param database The database. - * @return The list of view metadata. - * @throws SQLException The connection to the data database was unsuccessful. - * @throws DatabaseMalformedException The columns that are referenced in the views are unknown to the Metadata Database. Call {@link TableService#getSchemas(PrivilegedDatabaseDto)} beforehand. - * @throws ViewNotFoundException The view with given name was not found. - */ - List<ViewDto> getSchemas(PrivilegedDatabaseDto database) throws SQLException, DatabaseMalformedException, - ViewNotFoundException; - - /** - * Creates a view if not already exists. - * @param database - * @param subset - * @return - * @throws ViewMalformedException - * @throws SQLException - */ - ViewDto create(PrivilegedDatabaseDto database, QueryDto subset) throws ViewMalformedException, SQLException; - - /** - * Creates a view in the given data database. - * - * @param database The data database. - * @param data The view. - * @throws SQLException The connection to the data database was unsuccessful. - * @throws ViewMalformedException The query is malformed and was rejected by the data database. - */ - ViewDto create(PrivilegedDatabaseDto database, ViewCreateDto data) throws SQLException, - ViewMalformedException; - /** * Deletes a view. * - * @param database The database. - * @param viewName The view name. + * @param view The view. * @throws SQLException The connection to the data database was unsuccessful. * @throws ViewMalformedException The query is malformed and was rejected by the data database. */ - void delete(PrivilegedDatabaseDto database, String viewName) throws SQLException, ViewMalformedException; + void delete(ViewDto view) throws SQLException, ViewMalformedException; /** * Counts tuples in a view at system-versioned timestamp. @@ -71,5 +27,5 @@ public interface ViewService { * @throws SQLException The connection to the data database was unsuccessful. * @throws QueryMalformedException The query is malformed and was rejected by the data database. */ - Long count(PrivilegedViewDto view, Instant timestamp) throws SQLException, QueryMalformedException; + Long count(ViewDto view, Instant timestamp) throws SQLException, QueryMalformedException; } diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/AccessServiceMariaDbImpl.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/AccessServiceMariaDbImpl.java index c50ac2f0d6..16ace6dc9e 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/AccessServiceMariaDbImpl.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/AccessServiceMariaDbImpl.java @@ -1,8 +1,8 @@ package at.tuwien.service.impl; import at.tuwien.api.database.AccessTypeDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; -import at.tuwien.api.user.internal.PrivilegedUserDto; +import at.tuwien.api.database.DatabaseDto; +import at.tuwien.api.user.UserDto; import at.tuwien.exception.DatabaseMalformedException; import at.tuwien.mapper.MariaDbMapper; import at.tuwien.service.AccessService; @@ -17,7 +17,7 @@ import java.sql.SQLException; @Log4j2 @Service -public class AccessServiceMariaDbImpl extends HibernateConnector implements AccessService { +public class AccessServiceMariaDbImpl extends DataConnector<DatabaseDto> implements AccessService { @Value("${dbrepo.grant.default.read}") private String grantDefaultRead; @@ -33,9 +33,9 @@ public class AccessServiceMariaDbImpl extends HibernateConnector implements Acce } @Override - public void create(PrivilegedDatabaseDto database, PrivilegedUserDto user, AccessTypeDto access) + public void create(DatabaseDto database, UserDto user, AccessTypeDto access) throws SQLException, DatabaseMalformedException { - final ComboPooledDataSource dataSource = getPrivilegedDataSource(database); + final ComboPooledDataSource dataSource = getDataSource(database); final Connection connection = dataSource.getConnection(); try { /* create user if not exists */ @@ -71,9 +71,9 @@ public class AccessServiceMariaDbImpl extends HibernateConnector implements Acce } @Override - public void update(PrivilegedDatabaseDto database, PrivilegedUserDto user, AccessTypeDto access) + public void update(DatabaseDto database, UserDto user, AccessTypeDto access) throws DatabaseMalformedException, SQLException { - final ComboPooledDataSource dataSource = getPrivilegedDataSource(database); + final ComboPooledDataSource dataSource = getDataSource(database); final Connection connection = dataSource.getConnection(); try { /* grant access */ @@ -96,9 +96,9 @@ public class AccessServiceMariaDbImpl extends HibernateConnector implements Acce } @Override - public void delete(PrivilegedDatabaseDto database, PrivilegedUserDto user) throws DatabaseMalformedException, + public void delete(DatabaseDto database, UserDto user) throws DatabaseMalformedException, SQLException { - final ComboPooledDataSource dataSource = getPrivilegedDataSource(database); + final ComboPooledDataSource dataSource = getDataSource(database); final Connection connection = dataSource.getConnection(); try { /* revoke access */ @@ -109,7 +109,7 @@ public class AccessServiceMariaDbImpl extends HibernateConnector implements Acce /* apply access rights */ start = System.currentTimeMillis(); connection.prepareStatement(mariaDbMapper.databaseFlushPrivilegesQuery()) - .execute(); + .execute(); log.trace("executed statement in {} ms", System.currentTimeMillis() - start); connection.commit(); } catch (SQLException e) { diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/ContainerServiceMariaDbImpl.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/ContainerServiceMariaDbImpl.java new file mode 100644 index 0000000000..fc9b2d97c3 --- /dev/null +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/ContainerServiceMariaDbImpl.java @@ -0,0 +1,103 @@ +package at.tuwien.service.impl; + +import at.tuwien.api.container.ContainerDto; +import at.tuwien.api.database.DatabaseDto; +import at.tuwien.api.database.internal.CreateDatabaseDto; +import at.tuwien.api.user.UserBriefDto; +import at.tuwien.config.RabbitConfig; +import at.tuwien.exception.DatabaseMalformedException; +import at.tuwien.exception.QueryStoreCreateException; +import at.tuwien.mapper.MariaDbMapper; +import at.tuwien.service.ContainerService; +import com.mchange.v2.c3p0.ComboPooledDataSource; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.sql.Connection; +import java.sql.SQLException; + +@Log4j2 +@Service +public class ContainerServiceMariaDbImpl extends DataConnector<ContainerDto> implements ContainerService { + + private final RabbitConfig rabbitConfig; + private final MariaDbMapper mariaDbMapper; + + @Autowired + public ContainerServiceMariaDbImpl(RabbitConfig rabbitConfig, MariaDbMapper mariaDbMapper) { + this.rabbitConfig = rabbitConfig; + this.mariaDbMapper = mariaDbMapper; + } + + @Override + public DatabaseDto createDatabase(ContainerDto container, CreateDatabaseDto data) throws SQLException, + DatabaseMalformedException { + final ComboPooledDataSource dataSource = getDataSource(container); + final Connection connection = dataSource.getConnection(); + try { + /* create database if not exists */ + final long start = System.currentTimeMillis(); + connection.prepareStatement(mariaDbMapper.databaseCreateDatabaseQuery(data.getInternalName())) + .execute(); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); + connection.commit(); + } catch (SQLException e) { + connection.rollback(); + log.error("Failed to create database access: {}", e.getMessage()); + throw new DatabaseMalformedException("Failed to create database access: " + e.getMessage(), e); + } finally { + dataSource.close(); + } + log.info("Created database with name {}", data.getInternalName()); + return DatabaseDto.builder() + .internalName(data.getInternalName()) + .exchangeName(rabbitConfig.getExchangeName()) + .owner(UserBriefDto.builder() + .id(data.getUserId()) + .build()) + .contact(UserBriefDto.builder() + .id(data.getUserId()) + .build()) + .container(container) + .build(); + } + + @Override + public void createQueryStore(ContainerDto container, String databaseName) throws SQLException, + QueryStoreCreateException { + final ComboPooledDataSource dataSource = getDataSource(container, databaseName); + final Connection connection = dataSource.getConnection(); + try { + /* create query store */ + long start = System.currentTimeMillis(); + connection.prepareStatement(mariaDbMapper.queryStoreCreateSequenceRawQuery()) + .execute(); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); + start = System.currentTimeMillis(); + connection.prepareStatement(mariaDbMapper.queryStoreCreateTableRawQuery()) + .execute(); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); + start = System.currentTimeMillis(); + connection.prepareStatement(mariaDbMapper.queryStoreCreateHashTableProcedureRawQuery()) + .execute(); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); + start = System.currentTimeMillis(); + connection.prepareStatement(mariaDbMapper.queryStoreCreateStoreQueryProcedureRawQuery()) + .execute(); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); + start = System.currentTimeMillis(); + connection.prepareStatement(mariaDbMapper.queryStoreCreateInternalStoreQueryProcedureRawQuery()) + .execute(); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); + connection.commit(); + } catch (SQLException e) { + connection.rollback(); + log.error("Failed to create query store: {}", e.getMessage()); + throw new QueryStoreCreateException("Failed to create query store: " + e.getMessage(), e); + } finally { + dataSource.close(); + } + log.info("Created query store in database with name {}", databaseName); + } +} diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/CredentialServiceImpl.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/CredentialServiceImpl.java index 05b9e0a3fe..fbc800bf47 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/CredentialServiceImpl.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/CredentialServiceImpl.java @@ -1,11 +1,11 @@ package at.tuwien.service.impl; -import at.tuwien.api.container.internal.PrivilegedContainerDto; +import at.tuwien.api.container.ContainerDto; import at.tuwien.api.database.DatabaseAccessDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; -import at.tuwien.api.database.internal.PrivilegedViewDto; -import at.tuwien.api.database.table.internal.PrivilegedTableDto; -import at.tuwien.api.user.internal.PrivilegedUserDto; +import at.tuwien.api.database.DatabaseDto; +import at.tuwien.api.database.ViewDto; +import at.tuwien.api.database.table.TableDto; +import at.tuwien.api.user.UserDto; import at.tuwien.exception.*; import at.tuwien.gateway.MetadataServiceGateway; import at.tuwien.service.CredentialService; @@ -21,19 +21,19 @@ import java.util.UUID; public class CredentialServiceImpl implements CredentialService { private final MetadataServiceGateway gateway; - private final Cache<UUID, PrivilegedUserDto> userCache; - private final Cache<Long, PrivilegedViewDto> viewCache; + private final Cache<UUID, UserDto> userCache; + private final Cache<Long, ViewDto> viewCache; private final Cache<Long, DatabaseAccessDto> accessCache; - private final Cache<Long, PrivilegedTableDto> tableCache; - private final Cache<Long, PrivilegedDatabaseDto> databaseCache; - private final Cache<Long, PrivilegedContainerDto> containerCache; + private final Cache<Long, TableDto> tableCache; + private final Cache<Long, DatabaseDto> databaseCache; + private final Cache<Long, ContainerDto> containerCache; @Autowired - public CredentialServiceImpl(MetadataServiceGateway gateway, Cache<UUID, PrivilegedUserDto> userCache, - Cache<Long, PrivilegedViewDto> viewCache, Cache<Long, DatabaseAccessDto> accessCache, - Cache<Long, PrivilegedTableDto> tableCache, - Cache<Long, PrivilegedDatabaseDto> databaseCache, - Cache<Long, PrivilegedContainerDto> containerCache) { + public CredentialServiceImpl(MetadataServiceGateway gateway, Cache<UUID, UserDto> userCache, + Cache<Long, ViewDto> viewCache, Cache<Long, DatabaseAccessDto> accessCache, + Cache<Long, TableDto> tableCache, + Cache<Long, DatabaseDto> databaseCache, + Cache<Long, ContainerDto> containerCache) { this.gateway = gateway; this.userCache = userCache; this.viewCache = viewCache; @@ -44,29 +44,29 @@ public class CredentialServiceImpl implements CredentialService { } @Override - public PrivilegedDatabaseDto getDatabase(Long id) throws DatabaseNotFoundException, RemoteUnavailableException, + public DatabaseDto getDatabase(Long id) throws DatabaseNotFoundException, RemoteUnavailableException, MetadataServiceException { - final PrivilegedDatabaseDto cacheDatabase = databaseCache.getIfPresent(id); + final DatabaseDto cacheDatabase = databaseCache.getIfPresent(id); if (cacheDatabase != null) { log.trace("found database with id {} in cache", id); return cacheDatabase; } log.debug("database with id {} not it cache (anymore): reload from metadata service", id); - final PrivilegedDatabaseDto database = gateway.getDatabaseById(id); + final DatabaseDto database = gateway.getDatabaseById(id); databaseCache.put(id, database); return database; } @Override - public PrivilegedTableDto getTable(Long databaseId, Long tableId) throws RemoteUnavailableException, + public TableDto getTable(Long databaseId, Long tableId) throws RemoteUnavailableException, MetadataServiceException, TableNotFoundException { - final PrivilegedTableDto cacheTable = tableCache.getIfPresent(tableId); + final TableDto cacheTable = tableCache.getIfPresent(tableId); if (cacheTable != null) { log.trace("found table with id {} in cache", tableId); return cacheTable; } log.debug("table with id {} not it cache (anymore): reload from metadata service", tableId); - final PrivilegedTableDto table = gateway.getTableById(databaseId, tableId); + final TableDto table = gateway.getTableById(databaseId, tableId); tableCache.put(tableId, table); return table; } @@ -78,43 +78,43 @@ public class CredentialServiceImpl implements CredentialService { } @Override - public PrivilegedContainerDto getContainer(Long id) throws RemoteUnavailableException, MetadataServiceException, + public ContainerDto getContainer(Long id) throws RemoteUnavailableException, MetadataServiceException, ContainerNotFoundException { - final PrivilegedContainerDto cacheContainer = containerCache.getIfPresent(id); + final ContainerDto cacheContainer = containerCache.getIfPresent(id); if (cacheContainer != null) { log.trace("found container with id {} in cache", id); return cacheContainer; } log.debug("container with id {} not it cache (anymore): reload from metadata service", id); - final PrivilegedContainerDto container = gateway.getContainerById(id); + final ContainerDto container = gateway.getContainerById(id); containerCache.put(id, container); return container; } @Override - public PrivilegedViewDto getView(Long databaseId, Long viewId) throws RemoteUnavailableException, + public ViewDto getView(Long databaseId, Long viewId) throws RemoteUnavailableException, MetadataServiceException, ViewNotFoundException { - final PrivilegedViewDto cacheView = viewCache.getIfPresent(viewId); + final ViewDto cacheView = viewCache.getIfPresent(viewId); if (cacheView != null) { log.trace("found view with id {} in cache", viewId); return cacheView; } log.debug("view with id {} not it cache (anymore): reload from metadata service", viewId); - final PrivilegedViewDto view = gateway.getViewById(databaseId, viewId); + final ViewDto view = gateway.getViewById(databaseId, viewId); viewCache.put(viewId, view); return view; } @Override - public PrivilegedUserDto getUser(UUID id) throws RemoteUnavailableException, MetadataServiceException, + public UserDto getUser(UUID id) throws RemoteUnavailableException, MetadataServiceException, UserNotFoundException { - final PrivilegedUserDto cacheUser = userCache.getIfPresent(id); + final UserDto cacheUser = userCache.getIfPresent(id); if (cacheUser != null) { log.trace("found user with id {} in cache", id); return cacheUser; } log.debug("user with id {} not it cache (anymore): reload from metadata service", id); - final PrivilegedUserDto user = gateway.getPrivilegedUserById(id); + final UserDto user = gateway.getUserById(id); userCache.put(id, user); return user; } diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/DataConnector.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/DataConnector.java new file mode 100644 index 0000000000..9fbee30fa6 --- /dev/null +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/DataConnector.java @@ -0,0 +1,66 @@ +package at.tuwien.service.impl; + +import at.tuwien.api.CacheableDto; +import com.mchange.v2.c3p0.ComboPooledDataSource; +import lombok.extern.log4j.Log4j2; +import org.springframework.stereotype.Service; + +@Log4j2 +@Service +public abstract class DataConnector<T extends CacheableDto> { + + public ComboPooledDataSource getDataSource(T entity) { + final long start = System.currentTimeMillis(); + final ComboPooledDataSource dataSource = new ComboPooledDataSource(); + dataSource.setJdbcUrl(getJdbcUrl(entity.getJdbcMethod(), entity.getHost(), entity.getPassword(), + entity.getDatabase())); + dataSource.setUser(entity.getUsername()); + dataSource.setPassword(entity.getPassword()); + dataSource.setInitialPoolSize(5); + dataSource.setMinPoolSize(5); + dataSource.setAcquireIncrement(5); + dataSource.setMaxPoolSize(20); + dataSource.setMaxStatements(100); + return dataSource; + } + + public ComboPooledDataSource getDataSource(T entity, String databaseName) { + final long start = System.currentTimeMillis(); + final ComboPooledDataSource dataSource = new ComboPooledDataSource(); + dataSource.setJdbcUrl(getJdbcUrl(entity.getJdbcMethod(), entity.getHost(), entity.getPassword(), databaseName)); + dataSource.setUser(entity.getUsername()); + dataSource.setPassword(entity.getPassword()); + dataSource.setInitialPoolSize(5); + dataSource.setMinPoolSize(5); + dataSource.setAcquireIncrement(5); + dataSource.setMaxPoolSize(20); + dataSource.setMaxStatements(100); + return dataSource; + } + + public String getSparkUrl(String jdbcMethod, String host, String password, String databaseName) { + final StringBuilder sb = new StringBuilder(getJdbcUrl(jdbcMethod, host, password, databaseName)) + .append("?sessionVariables=sql_mode='ANSI_QUOTES'"); + log.trace("mapped container to spark url: {}", sb.toString()); + return sb.toString(); + } + + public String getSparkUrl(T entity) { + return getSparkUrl(entity.getJdbcMethod(), entity.getHost(), entity.getPassword(), entity.getDatabase()); + } + + public String getJdbcUrl(String jdbcMethod, String host, String password, String databaseName) { + final StringBuilder stringBuilder = new StringBuilder("jdbc:") + .append(jdbcMethod) + .append("://") + .append(host) + .append(":") + .append(password); + if (databaseName != null) { + stringBuilder.append("/") + .append(databaseName); + } + return stringBuilder.toString(); + } + +} diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/DatabaseServiceMariaDbImpl.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/DatabaseServiceMariaDbImpl.java index a15c22f55e..2b5af71d1c 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/DatabaseServiceMariaDbImpl.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/DatabaseServiceMariaDbImpl.java @@ -1,72 +1,353 @@ package at.tuwien.service.impl; -import at.tuwien.api.container.internal.PrivilegedContainerDto; -import at.tuwien.api.database.internal.CreateDatabaseDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; -import at.tuwien.api.user.UserBriefDto; +import at.tuwien.api.database.DatabaseDto; +import at.tuwien.api.database.ViewCreateDto; +import at.tuwien.api.database.ViewDto; +import at.tuwien.api.database.table.TableDto; +import at.tuwien.api.database.table.constraints.unique.UniqueDto; +import at.tuwien.api.database.table.internal.TableCreateDto; import at.tuwien.api.user.internal.UpdateUserPasswordDto; -import at.tuwien.config.RabbitConfig; -import at.tuwien.exception.DatabaseMalformedException; +import at.tuwien.config.QueryConfig; +import at.tuwien.exception.*; +import at.tuwien.mapper.DataMapper; import at.tuwien.mapper.MariaDbMapper; +import at.tuwien.mapper.MetadataMapper; import at.tuwien.service.DatabaseService; +import at.tuwien.service.TableService; +import at.tuwien.service.ViewService; +import com.google.common.hash.Hashing; import com.mchange.v2.c3p0.ComboPooledDataSource; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; +import java.nio.charset.StandardCharsets; import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; +import java.util.LinkedList; +import java.util.List; @Log4j2 @Service -public class DatabaseServiceMariaDbImpl extends HibernateConnector implements DatabaseService { +public class DatabaseServiceMariaDbImpl extends DataConnector<DatabaseDto> implements DatabaseService { - private final RabbitConfig rabbitConfig; + private final DataMapper dataMapper; + private final QueryConfig queryConfig; + private final ViewService viewService; + private final TableService tableService; private final MariaDbMapper mariaDbMapper; + private final MetadataMapper metadataMapper; @Autowired - public DatabaseServiceMariaDbImpl(RabbitConfig rabbitConfig, MariaDbMapper mariaDbMapper) { - this.rabbitConfig = rabbitConfig; + public DatabaseServiceMariaDbImpl(DataMapper dataMapper, QueryConfig queryConfig, ViewService viewService, + TableService tableService, MariaDbMapper mariaDbMapper, + @Qualifier("metadataMapper") MetadataMapper metadataMapper) { + this.dataMapper = dataMapper; + this.queryConfig = queryConfig; + this.viewService = viewService; + this.tableService = tableService; this.mariaDbMapper = mariaDbMapper; + this.metadataMapper = metadataMapper; } @Override - public PrivilegedDatabaseDto create(PrivilegedContainerDto container, CreateDatabaseDto data) throws SQLException, - DatabaseMalformedException { - final ComboPooledDataSource dataSource = getPrivilegedDataSource(container, null); + public ViewDto inspectView(DatabaseDto database, String viewName) throws SQLException, ViewNotFoundException { + final ComboPooledDataSource dataSource = getDataSource(database); + final Connection connection = dataSource.getConnection(); + try { + /* obtain only view metadata */ + long start = System.currentTimeMillis(); + final PreparedStatement statement1 = connection.prepareStatement(mariaDbMapper.databaseViewSelectRawQuery()); + statement1.setString(1, database.getInternalName()); + statement1.setString(2, viewName); + log.trace("1={}, 2={}", database.getInternalName(), viewName); + final ResultSet resultSet1 = statement1.executeQuery(); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); + if (!resultSet1.next()) { + throw new ViewNotFoundException("Failed to find view in the information schema"); + } + final ViewDto view = dataMapper.schemaResultSetToView(database, resultSet1); + view.setVdbid(database.getId()); + view.setOwner(database.getOwner()); + /* obtain view columns */ + start = System.currentTimeMillis(); + final PreparedStatement statement2 = connection.prepareStatement(mariaDbMapper.databaseTableColumnsSelectRawQuery()); + statement2.setString(1, database.getInternalName()); + statement2.setString(2, view.getInternalName()); + log.trace("1={}, 2={}", database.getInternalName(), view.getInternalName()); + final ResultSet resultSet2 = statement2.executeQuery(); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); + TableDto tmp = TableDto.builder() + .columns(new LinkedList<>()) + .build(); + while (resultSet2.next()) { + tmp = dataMapper.resultSetToTable(resultSet2, tmp); + } + view.setColumns(tmp.getColumns() + .stream() + .map(metadataMapper::columnDtoToViewColumnDto) + .toList()); + view.getColumns() + .forEach(column -> column.setDatabaseId(database.getId())); + log.debug("obtained metadata for view {}.{}", database.getInternalName(), view.getInternalName()); + return view; + } finally { + dataSource.close(); + } + } + + @Override + public TableDto createTable(DatabaseDto database, TableCreateDto data) throws SQLException, + TableMalformedException, TableExistsException { + final String tableName = mariaDbMapper.nameToInternalName(data.getName()); + final ComboPooledDataSource dataSource = getDataSource(database); final Connection connection = dataSource.getConnection(); try { - /* create database if not exists */ + /* create table if not exists */ final long start = System.currentTimeMillis(); - connection.prepareStatement(mariaDbMapper.databaseCreateDatabaseQuery(data.getInternalName())) + connection.prepareStatement(mariaDbMapper.tableCreateDtoToCreateTableRawQuery(data)) .execute(); log.trace("executed statement in {} ms", System.currentTimeMillis() - start); connection.commit(); } catch (SQLException e) { connection.rollback(); - log.error("Failed to create database access: {}", e.getMessage()); - throw new DatabaseMalformedException("Failed to create database access: " + e.getMessage(), e); + if (e.getMessage().contains("already exists")) { + log.error("Failed to create table: already exists"); + throw new TableExistsException("Failed to create table: already exists", e); + } + log.error("Failed to create table: {}", e.getMessage()); + throw new TableMalformedException("Failed to create table: " + e.getMessage(), e); + } finally { + dataSource.close(); + } + log.info("Created table with name {}", tableName); + final TableDto table = metadataMapper.databaseDtoToTableDto(database); + table.setInternalName(tableName); + return table; + } + + @Override + public Boolean existsView(DatabaseDto database, String viewName) throws SQLException, + QueryMalformedException { + final ComboPooledDataSource dataSource = getDataSource(database); + final Connection connection = dataSource.getConnection(); + final Boolean queryResult; + try { + /* find view data */ + final long start = System.currentTimeMillis(); + final PreparedStatement statement = connection.prepareStatement(mariaDbMapper.selectExistsTableOrViewRawQuery()); + statement.setString(1, database.getInternalName()); + statement.setString(2, viewName); + final ResultSet resultSet = statement.executeQuery(); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); + queryResult = mariaDbMapper.resultSetToBoolean(resultSet); + } catch (SQLException e) { + log.error("Failed to prepare statement {}", e.getMessage()); + throw new QueryMalformedException("Failed to prepare statement: " + e.getMessage(), e); } finally { dataSource.close(); } - log.info("Created database with name {}", data.getInternalName()); - return PrivilegedDatabaseDto.builder() - .internalName(data.getInternalName()) - .exchangeName(rabbitConfig.getExchangeName()) - .owner(UserBriefDto.builder() - .id(data.getUserId()) - .build()) - .contact(UserBriefDto.builder() - .id(data.getUserId()) - .build()) - .container(container) + return queryResult; + } + + @Override + public ViewDto createView(DatabaseDto database, ViewCreateDto data) throws SQLException, + ViewMalformedException { + final ComboPooledDataSource dataSource = getDataSource(database); + final Connection connection = dataSource.getConnection(); + ViewDto view = ViewDto.builder() + .name(data.getName()) + .internalName(mariaDbMapper.nameToInternalName(data.getName())) + .query(data.getQuery()) + .queryHash(Hashing.sha256() + .hashString(data.getQuery(), StandardCharsets.UTF_8) + .toString()) + .isPublic(database.getIsPublic()) + .owner(database.getOwner()) + .identifiers(new LinkedList<>()) + .isInitialView(false) + .vdbid(database.getId()) + .columns(new LinkedList<>()) .build(); + try { + /* create view if not exists */ + final long start = System.currentTimeMillis(); + connection.prepareStatement(mariaDbMapper.viewCreateRawQuery(view.getInternalName(), data.getQuery())) + .execute(); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); + /* select view columns */ + final PreparedStatement statement2 = connection.prepareStatement(mariaDbMapper.databaseTableColumnsSelectRawQuery()); + statement2.setString(1, database.getInternalName()); + statement2.setString(2, view.getInternalName()); + final ResultSet resultSet2 = statement2.executeQuery(); + while (resultSet2.next()) { + view = dataMapper.resultSetToTable(resultSet2, view, queryConfig); + } + connection.commit(); + } catch (SQLException e) { + connection.rollback(); + log.error("Failed to create view: {}", e.getMessage()); + throw new ViewMalformedException("Failed to create view: " + e.getMessage(), e); + } finally { + dataSource.close(); + } + log.info("Created view with name {}", view.getName()); + return view; + } + + @Override + public List<ViewDto> exploreViews(DatabaseDto database) throws SQLException, DatabaseMalformedException, + ViewNotFoundException { + final ComboPooledDataSource dataSource = getDataSource(database); + final Connection connection = dataSource.getConnection(); + final List<ViewDto> views = new LinkedList<>(); + try { + /* inspect tables before views */ + final PreparedStatement statement = connection.prepareStatement(mariaDbMapper.databaseViewsSelectRawQuery()); + statement.setString(1, database.getInternalName()); + final long start = System.currentTimeMillis(); + final ResultSet resultSet1 = statement.executeQuery(); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); + while (resultSet1.next()) { + final String viewName = resultSet1.getString(1); + if (viewName.length() == 64) { + log.trace("view {}.{} seems to be a subset view (name length = 64), skip.", database.getInternalName(), viewName); + continue; + } + if (database.getViews().stream().anyMatch(v -> v.getInternalName().equals(viewName))) { + log.trace("view {}.{} already known to metadata database, skip.", database.getInternalName(), viewName); + continue; + } + if (database.getTables().stream().noneMatch(t -> t.getInternalName().equals(viewName))) { + views.add(inspectView(database, viewName)); + } + } + } catch (SQLException e) { + log.error("Failed to get view schemas: {}", e.getMessage()); + throw new DatabaseMalformedException("Failed to get view schemas: " + e.getMessage(), e); + } finally { + dataSource.close(); + } + log.info("Found {} view schema(s)", views.size()); + return views; + } + + @Override + public List<TableDto> exploreTables(DatabaseDto database) throws SQLException, TableNotFoundException, + DatabaseMalformedException { + final ComboPooledDataSource dataSource = getDataSource(database); + final Connection connection = dataSource.getConnection(); + final List<TableDto> tables = new LinkedList<>(); + try { + /* inspect tables before views */ + final long start = System.currentTimeMillis(); + final PreparedStatement statement = connection.prepareStatement(mariaDbMapper.databaseTablesSelectRawQuery()); + statement.setString(1, database.getInternalName()); + final ResultSet resultSet1 = statement.executeQuery(); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); + while (resultSet1.next()) { + final String tableName = resultSet1.getString(1); + if (database.getTables().stream().anyMatch(t -> t.getInternalName().equals(tableName))) { + log.trace("view {}.{} already known to metadata database, skip.", database.getInternalName(), tableName); + continue; + } + final TableDto table = inspectTable(database, tableName); + if (database.getTables().stream().noneMatch(t -> t.getInternalName().equals(tableName))) { + tables.add(table); + } + } + } catch (SQLException e) { + log.error("Failed to get table schemas: {}", e.getMessage()); + throw new DatabaseMalformedException("Failed to get table schemas: " + e.getMessage(), e); + } finally { + dataSource.close(); + } + log.info("Found {} table schema(s)", tables.size()); + return tables; + } + + @Override + public TableDto inspectTable(DatabaseDto database, String tableName) throws SQLException, TableNotFoundException { + log.trace("inspecting table: {}.{}", database.getInternalName(), tableName); + final ComboPooledDataSource dataSource = getDataSource(database); + final Connection connection = dataSource.getConnection(); + try { + /* obtain only table metadata */ + long start = System.currentTimeMillis(); + final PreparedStatement statement1 = connection.prepareStatement(mariaDbMapper.databaseTableSelectRawQuery()); + statement1.setString(1, database.getInternalName()); + statement1.setString(2, tableName); + log.trace("1={}, 2={}", database.getInternalName(), tableName); + TableDto table = dataMapper.schemaResultSetToTable(database, statement1.executeQuery()); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); + /* obtain columns metadata */ + start = System.currentTimeMillis(); + final PreparedStatement statement2 = connection.prepareStatement(mariaDbMapper.databaseTableColumnsSelectRawQuery()); + statement2.setString(1, database.getInternalName()); + statement2.setString(2, tableName); + log.trace("1={}, 2={}", database.getInternalName(), tableName); + final ResultSet resultSet2 = statement2.executeQuery(); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); + while (resultSet2.next()) { + table = dataMapper.resultSetToTable(resultSet2, table); + } + /* obtain check constraints metadata */ + start = System.currentTimeMillis(); + final PreparedStatement statement3 = connection.prepareStatement(mariaDbMapper.columnsCheckConstraintSelectRawQuery()); + statement3.setString(1, database.getInternalName()); + statement3.setString(2, tableName); + log.trace("1={}, 2={}", database.getInternalName(), tableName); + final ResultSet resultSet3 = statement3.executeQuery(); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); + while (resultSet3.next()) { + final String clause = resultSet3.getString(1); + table.getConstraints() + .getChecks() + .add(clause); + log.trace("found check clause: {}", clause); + } + /* obtain column constraints metadata */ + start = System.currentTimeMillis(); + final PreparedStatement statement4 = connection.prepareStatement(mariaDbMapper.databaseTableConstraintsSelectRawQuery()); + statement4.setString(1, database.getInternalName()); + statement4.setString(2, tableName); + log.trace("1={}, 2={}", database.getInternalName(), tableName); + final ResultSet resultSet4 = statement4.executeQuery(); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); + while (resultSet4.next()) { + table = dataMapper.resultSetToConstraint(resultSet4, table); + for (UniqueDto uk : table.getConstraints().getUniques()) { + uk.setTable(metadataMapper.tableDtoToTableBriefDto(table)); + final TableDto tmpTable = table; + uk.getColumns() + .forEach(column -> { + column.setTableId(tmpTable.getId()); + column.setDatabaseId(database.getId()); + }); + } + } + table.setTdbid(database.getId()); + table.setOwner(database.getOwner()); + final TableDto tmpTable = table; + tmpTable.getColumns() + .forEach(column -> { + column.setTableId(tmpTable.getId()); + column.setDatabaseId(database.getId()); + }); + log.debug("obtained metadata for table {}.{}", database.getInternalName(), tableName); + return tmpTable; + } finally { + dataSource.close(); + } } @Override - public void update(PrivilegedDatabaseDto database, UpdateUserPasswordDto data) throws SQLException, + public void update(DatabaseDto database, UpdateUserPasswordDto data) throws SQLException, DatabaseMalformedException { - final ComboPooledDataSource dataSource = getPrivilegedDataSource(database); + final ComboPooledDataSource dataSource = getDataSource(database); final Connection connection = dataSource.getConnection(); try { /* update user password */ diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/HibernateConnector.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/HibernateConnector.java deleted file mode 100644 index 242cefd557..0000000000 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/HibernateConnector.java +++ /dev/null @@ -1,53 +0,0 @@ -package at.tuwien.service.impl; - -import at.tuwien.api.container.internal.PrivilegedContainerDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; -import com.mchange.v2.c3p0.ComboPooledDataSource; -import lombok.extern.log4j.Log4j2; -import org.springframework.stereotype.Service; - -@Log4j2 -@Service -public abstract class HibernateConnector { - - public ComboPooledDataSource getPrivilegedDataSource(PrivilegedContainerDto container, String databaseName) { - final long start = System.currentTimeMillis(); - final ComboPooledDataSource dataSource = new ComboPooledDataSource(); - dataSource.setJdbcUrl(url(container, databaseName)); - dataSource.setUser(container.getUsername()); - dataSource.setPassword(container.getPassword()); - dataSource.setInitialPoolSize(5); - dataSource.setMinPoolSize(5); - dataSource.setAcquireIncrement(5); - dataSource.setMaxPoolSize(20); - dataSource.setMaxStatements(100); - log.trace("created pooled data source {} in {} ms, user={}", url(container, databaseName), System.currentTimeMillis() - start, container.getUsername()); - return dataSource; - } - - public ComboPooledDataSource getPrivilegedDataSource(PrivilegedDatabaseDto database) { - return getPrivilegedDataSource(database.getContainer(), database.getInternalName()); - } - - public String getSparkUrl(PrivilegedContainerDto container, String databaseName) { - final StringBuilder sb = new StringBuilder(url(container, databaseName)) - .append("?sessionVariables=sql_mode='ANSI_QUOTES'"); - log.trace("mapped container to spark url: {}", sb.toString()); - return sb.toString(); - } - - private String url(PrivilegedContainerDto container, String databaseName) { - final StringBuilder stringBuilder = new StringBuilder("jdbc:") - .append(container.getImage().getJdbcMethod()) - .append("://") - .append(container.getHost()) - .append(":") - .append(container.getPort()); - if (databaseName != null) { - stringBuilder.append("/") - .append(databaseName); - } - return stringBuilder.toString(); - } - -} diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/QueueServiceRabbitMqImpl.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/QueueServiceRabbitMqImpl.java index d5127e050e..1a26a84ef6 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/QueueServiceRabbitMqImpl.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/QueueServiceRabbitMqImpl.java @@ -1,7 +1,7 @@ package at.tuwien.service.impl; +import at.tuwien.api.database.table.TableDto; import at.tuwien.api.database.table.columns.ColumnDto; -import at.tuwien.api.database.table.internal.PrivilegedTableDto; import at.tuwien.mapper.DataMapper; import at.tuwien.mapper.MetadataMapper; import at.tuwien.service.QueueService; @@ -18,7 +18,7 @@ import java.util.Optional; @Log4j2 @Service -public class QueueServiceRabbitMqImpl extends HibernateConnector implements QueueService { +public class QueueServiceRabbitMqImpl extends DataConnector<TableDto> implements QueueService { private final DataMapper dataMapper; private final MetadataMapper metadataMapper; @@ -30,13 +30,13 @@ public class QueueServiceRabbitMqImpl extends HibernateConnector implements Queu } @Override - public void insert(PrivilegedTableDto table, Map<String, Object> data) throws SQLException { - final ComboPooledDataSource dataSource = getPrivilegedDataSource(table.getDatabase()); + public void insert(TableDto table, Map<String, Object> data) throws SQLException { + final ComboPooledDataSource dataSource = getDataSource(table); final Connection connection = dataSource.getConnection(); try { final int[] idx = new int[]{1}; final PreparedStatement preparedStatement = connection.prepareStatement( - dataMapper.rabbitMqTupleToInsertOrUpdateQuery(metadataMapper.privilegedTableDtoToTableDto(table), data)); + dataMapper.rabbitMqTupleToInsertOrUpdateQuery(metadataMapper.tableDtoToTableDto(table), data)); for (Map.Entry<String, Object> entry : data.entrySet()) { final Optional<ColumnDto> optional = table.getColumns().stream().filter(c -> c.getInternalName().equals(entry.getKey())).findFirst(); if (optional.isEmpty()) { diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/SchemaServiceMariaDbImpl.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/SchemaServiceMariaDbImpl.java deleted file mode 100644 index e2b0c984e0..0000000000 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/SchemaServiceMariaDbImpl.java +++ /dev/null @@ -1,164 +0,0 @@ -package at.tuwien.service.impl; - -import at.tuwien.api.database.DatabaseDto; -import at.tuwien.api.database.ViewDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; -import at.tuwien.api.database.table.TableDto; -import at.tuwien.api.database.table.constraints.unique.UniqueDto; -import at.tuwien.exception.TableNotFoundException; -import at.tuwien.exception.ViewNotFoundException; -import at.tuwien.mapper.DataMapper; -import at.tuwien.mapper.MariaDbMapper; -import at.tuwien.mapper.MetadataMapper; -import at.tuwien.service.SchemaService; -import com.mchange.v2.c3p0.ComboPooledDataSource; -import lombok.extern.log4j.Log4j2; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.LinkedList; - -@Log4j2 -@Service -public class SchemaServiceMariaDbImpl extends HibernateConnector implements SchemaService { - - private final DataMapper dataMapper; - private final MariaDbMapper mariaDbMapper; - private final MetadataMapper metadataMapper; - - @Autowired - public SchemaServiceMariaDbImpl(DataMapper dataMapper, MariaDbMapper mariaDbMapper, MetadataMapper metadataMapper) { - this.dataMapper = dataMapper; - this.mariaDbMapper = mariaDbMapper; - this.metadataMapper = metadataMapper; - } - - @Override - public TableDto inspectTable(PrivilegedDatabaseDto database, String tableName) throws SQLException, - TableNotFoundException { - log.trace("inspecting table: {}.{}", database.getInternalName(), tableName); - final ComboPooledDataSource dataSource = getPrivilegedDataSource(database); - final Connection connection = dataSource.getConnection(); - try { - /* obtain only table metadata */ - long start = System.currentTimeMillis(); - final PreparedStatement statement1 = connection.prepareStatement(mariaDbMapper.databaseTableSelectRawQuery()); - statement1.setString(1, database.getInternalName()); - statement1.setString(2, tableName); - log.trace("1={}, 2={}", database.getInternalName(), tableName); - TableDto table = dataMapper.schemaResultSetToTable(metadataMapper.privilegedDatabaseDtoToDatabaseDto(database), statement1.executeQuery()); - log.trace("executed statement in {} ms", System.currentTimeMillis() - start); - /* obtain columns metadata */ - start = System.currentTimeMillis(); - final PreparedStatement statement2 = connection.prepareStatement(mariaDbMapper.databaseTableColumnsSelectRawQuery()); - statement2.setString(1, database.getInternalName()); - statement2.setString(2, tableName); - log.trace("1={}, 2={}", database.getInternalName(), tableName); - final ResultSet resultSet2 = statement2.executeQuery(); - log.trace("executed statement in {} ms", System.currentTimeMillis() - start); - while (resultSet2.next()) { - table = dataMapper.resultSetToTable(resultSet2, table); - } - /* obtain check constraints metadata */ - start = System.currentTimeMillis(); - final PreparedStatement statement3 = connection.prepareStatement(mariaDbMapper.columnsCheckConstraintSelectRawQuery()); - statement3.setString(1, database.getInternalName()); - statement3.setString(2, tableName); - log.trace("1={}, 2={}", database.getInternalName(), tableName); - final ResultSet resultSet3 = statement3.executeQuery(); - log.trace("executed statement in {} ms", System.currentTimeMillis() - start); - while (resultSet3.next()) { - final String clause = resultSet3.getString(1); - table.getConstraints() - .getChecks() - .add(clause); - log.trace("found check clause: {}", clause); - } - /* obtain column constraints metadata */ - start = System.currentTimeMillis(); - final PreparedStatement statement4 = connection.prepareStatement(mariaDbMapper.databaseTableConstraintsSelectRawQuery()); - statement4.setString(1, database.getInternalName()); - statement4.setString(2, tableName); - log.trace("1={}, 2={}", database.getInternalName(), tableName); - final ResultSet resultSet4 = statement4.executeQuery(); - log.trace("executed statement in {} ms", System.currentTimeMillis() - start); - while (resultSet4.next()) { - table = dataMapper.resultSetToConstraint(resultSet4, table); - for (UniqueDto uk : table.getConstraints().getUniques()) { - uk.setTable(metadataMapper.tableDtoToTableBriefDto(table)); - final TableDto tmpTable = table; - uk.getColumns() - .forEach(column -> { - column.setTableId(tmpTable.getId()); - column.setDatabaseId(database.getId()); - }); - } - } - table.setTdbid(database.getId()); - table.setOwner(database.getOwner()); - final TableDto tmpTable = table; - tmpTable.getColumns() - .forEach(column -> { - column.setTableId(tmpTable.getId()); - column.setDatabaseId(database.getId()); - }); - log.debug("obtained metadata for table {}.{}", database.getInternalName(), tableName); - return tmpTable; - } finally { - dataSource.close(); - } - } - - @Override - public ViewDto inspectView(PrivilegedDatabaseDto privilegedDatabase, String viewName) throws SQLException, - ViewNotFoundException { - final ComboPooledDataSource dataSource = getPrivilegedDataSource(privilegedDatabase); - final Connection connection = dataSource.getConnection(); - final DatabaseDto database = metadataMapper.privilegedDatabaseDtoToDatabaseDto(privilegedDatabase); - try { - /* obtain only view metadata */ - long start = System.currentTimeMillis(); - final PreparedStatement statement1 = connection.prepareStatement(mariaDbMapper.databaseViewSelectRawQuery()); - statement1.setString(1, database.getInternalName()); - statement1.setString(2, viewName); - log.trace("1={}, 2={}", database.getInternalName(), viewName); - final ResultSet resultSet1 = statement1.executeQuery(); - log.trace("executed statement in {} ms", System.currentTimeMillis() - start); - if (!resultSet1.next()) { - throw new ViewNotFoundException("Failed to find view in the information schema"); - } - ViewDto view = dataMapper.schemaResultSetToView(database, resultSet1); - view.setVdbid(database.getId()); - view.setOwner(database.getOwner()); - /* obtain view columns */ - start = System.currentTimeMillis(); - final PreparedStatement statement2 = connection.prepareStatement(mariaDbMapper.databaseTableColumnsSelectRawQuery()); - statement2.setString(1, database.getInternalName()); - statement2.setString(2, viewName); - log.trace("1={}, 2={}", database.getInternalName(), viewName); - final ResultSet resultSet2 = statement2.executeQuery(); - log.trace("executed statement in {} ms", System.currentTimeMillis() - start); - TableDto tmp = TableDto.builder() - .columns(new LinkedList<>()) - .build(); - while (resultSet2.next()) { - tmp = dataMapper.resultSetToTable(resultSet2, tmp); - } - view.setColumns(tmp.getColumns() - .stream() - .map(metadataMapper::columnDtoToViewColumnDto) - .toList()); - view.getColumns() - .forEach(column -> column.setDatabaseId(database.getId())); - log.debug("obtained metadata for view {}.{}", database.getInternalName(), viewName); - return view; - } finally { - dataSource.close(); - } - } - -} diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/SubsetServiceMariaDbImpl.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/SubsetServiceMariaDbImpl.java index fb244bb301..62b7c77a84 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/SubsetServiceMariaDbImpl.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/SubsetServiceMariaDbImpl.java @@ -1,7 +1,7 @@ package at.tuwien.service.impl; -import at.tuwien.api.container.internal.PrivilegedContainerDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; +import at.tuwien.api.database.DatabaseDto; +import at.tuwien.api.database.ViewCreateDto; import at.tuwien.api.database.query.QueryDto; import at.tuwien.api.identifier.IdentifierBriefDto; import at.tuwien.api.identifier.IdentifierTypeDto; @@ -10,6 +10,7 @@ import at.tuwien.gateway.MetadataServiceGateway; import at.tuwien.mapper.DataMapper; import at.tuwien.mapper.MariaDbMapper; import at.tuwien.mapper.MetadataMapper; +import at.tuwien.service.DatabaseService; import at.tuwien.service.SubsetService; import at.tuwien.service.TableService; import at.tuwien.service.ViewService; @@ -28,72 +29,41 @@ import java.util.UUID; @Log4j2 @Service -public class SubsetServiceMariaDbImpl extends HibernateConnector implements SubsetService { +public class SubsetServiceMariaDbImpl extends DataConnector<DatabaseDto> implements SubsetService { private final DataMapper dataMapper; private final ViewService viewService; private final TableService tableService; private final MariaDbMapper mariaDbMapper; private final MetadataMapper metadataMapper; + private final DatabaseService databaseService; private final MetadataServiceGateway metadataServiceGateway; @Autowired public SubsetServiceMariaDbImpl(DataMapper dataMapper, ViewService viewService, TableService tableService, MariaDbMapper mariaDbMapper, MetadataMapper metadataMapper, - MetadataServiceGateway metadataServiceGateway) { + DatabaseService databaseService, MetadataServiceGateway metadataServiceGateway) { this.dataMapper = dataMapper; this.viewService = viewService; this.tableService = tableService; this.mariaDbMapper = mariaDbMapper; this.metadataMapper = metadataMapper; + this.databaseService = databaseService; this.metadataServiceGateway = metadataServiceGateway; } @Override - public void createQueryStore(PrivilegedContainerDto container, String databaseName) throws SQLException, - QueryStoreCreateException { - final ComboPooledDataSource dataSource = getPrivilegedDataSource(container, databaseName); - final Connection connection = dataSource.getConnection(); - try { - /* create query store */ - long start = System.currentTimeMillis(); - connection.prepareStatement(mariaDbMapper.queryStoreCreateSequenceRawQuery()) - .execute(); - log.trace("executed statement in {} ms", System.currentTimeMillis() - start); - start = System.currentTimeMillis(); - connection.prepareStatement(mariaDbMapper.queryStoreCreateTableRawQuery()) - .execute(); - log.trace("executed statement in {} ms", System.currentTimeMillis() - start); - start = System.currentTimeMillis(); - connection.prepareStatement(mariaDbMapper.queryStoreCreateHashTableProcedureRawQuery()) - .execute(); - log.trace("executed statement in {} ms", System.currentTimeMillis() - start); - start = System.currentTimeMillis(); - connection.prepareStatement(mariaDbMapper.queryStoreCreateStoreQueryProcedureRawQuery()) - .execute(); - log.trace("executed statement in {} ms", System.currentTimeMillis() - start); - start = System.currentTimeMillis(); - connection.prepareStatement(mariaDbMapper.queryStoreCreateInternalStoreQueryProcedureRawQuery()) - .execute(); - log.trace("executed statement in {} ms", System.currentTimeMillis() - start); - connection.commit(); - } catch (SQLException e) { - connection.rollback(); - log.error("Failed to create query store: {}", e.getMessage()); - throw new QueryStoreCreateException("Failed to create query store: " + e.getMessage(), e); - } finally { - dataSource.close(); - } - log.info("Created query store in database with name {}", databaseName); - } - - @Override - public Dataset<Row> getData(PrivilegedDatabaseDto database, QueryDto subset, Long page, Long size) + public Dataset<Row> getData(DatabaseDto database, QueryDto subset, Long page, Long size) throws ViewMalformedException, SQLException, QueryMalformedException, TableNotFoundException { final String viewName = metadataMapper.queryDtoToViewName(subset); - if (!viewService.existsByName(database, viewName)) { + if (!databaseService.existsView(database, viewName)) { log.warn("Missing internal view {} for subset with id {}: create it from subset query", viewName, subset.getId()); - viewService.create(database, subset); + databaseService.createView(database, ViewCreateDto.builder() + .isPublic(false) + .isSchemaPublic(false) + .name(viewName) + .query(subset.getQuery()) + .build()); } else { log.debug("internal view {}.{} for subset with id {} exists", database.getInternalName(), viewName, subset.getId()); } @@ -101,22 +71,22 @@ public class SubsetServiceMariaDbImpl extends HibernateConnector implements Subs } @Override - public Long create(PrivilegedDatabaseDto database, String statement, Instant timestamp, UUID userId) + public Long create(DatabaseDto database, String statement, Instant timestamp, UUID userId) throws QueryStoreInsertException, SQLException { return storeQuery(database, statement, timestamp, userId); } @Override - public Long reExecuteCount(PrivilegedDatabaseDto database, QueryDto query) throws TableMalformedException, + public Long reExecuteCount(DatabaseDto database, QueryDto query) throws TableMalformedException, SQLException, QueryMalformedException { return executeCountNonPersistent(database, query.getQuery(), query.getExecution()); } @Override - public List<QueryDto> findAll(PrivilegedDatabaseDto database, Boolean filterPersisted) throws SQLException, + public List<QueryDto> findAll(DatabaseDto database, Boolean filterPersisted) throws SQLException, QueryNotFoundException, RemoteUnavailableException, DatabaseNotFoundException, MetadataServiceException { final List<IdentifierBriefDto> identifiers = metadataServiceGateway.getIdentifiers(database.getId(), null); - final ComboPooledDataSource dataSource = getPrivilegedDataSource(database); + final ComboPooledDataSource dataSource = getDataSource(database); final Connection connection = dataSource.getConnection(); try { final long start = System.currentTimeMillis(); @@ -147,9 +117,9 @@ public class SubsetServiceMariaDbImpl extends HibernateConnector implements Subs } @Override - public Long executeCountNonPersistent(PrivilegedDatabaseDto database, String statement, Instant timestamp) + public Long executeCountNonPersistent(DatabaseDto database, String statement, Instant timestamp) throws SQLException, QueryMalformedException, TableMalformedException { - final ComboPooledDataSource dataSource = getPrivilegedDataSource(database); + final ComboPooledDataSource dataSource = getDataSource(database); final Connection connection = dataSource.getConnection(); try { final long start = System.currentTimeMillis(); @@ -166,9 +136,9 @@ public class SubsetServiceMariaDbImpl extends HibernateConnector implements Subs } @Override - public QueryDto findById(PrivilegedDatabaseDto database, Long queryId) throws QueryNotFoundException, SQLException, + public QueryDto findById(DatabaseDto database, Long queryId) throws QueryNotFoundException, SQLException, RemoteUnavailableException, DatabaseNotFoundException, MetadataServiceException { - final ComboPooledDataSource dataSource = getPrivilegedDataSource(database); + final ComboPooledDataSource dataSource = getDataSource(database); final Connection connection = dataSource.getConnection(); try { final long start = System.currentTimeMillis(); @@ -193,11 +163,11 @@ public class SubsetServiceMariaDbImpl extends HibernateConnector implements Subs } @Override - public Long storeQuery(PrivilegedDatabaseDto database, String query, Instant timestamp, UUID userId) throws SQLException, + public Long storeQuery(DatabaseDto database, String query, Instant timestamp, UUID userId) throws SQLException, QueryStoreInsertException { /* save */ final Long queryId; - final ComboPooledDataSource dataSource = getPrivilegedDataSource(database); + final ComboPooledDataSource dataSource = getDataSource(database); final Connection connection = dataSource.getConnection(); try { /* insert query into query store */ @@ -228,9 +198,9 @@ public class SubsetServiceMariaDbImpl extends HibernateConnector implements Subs } @Override - public void persist(PrivilegedDatabaseDto database, Long queryId, Boolean persist) throws SQLException, + public void persist(DatabaseDto database, Long queryId, Boolean persist) throws SQLException, QueryStorePersistException { - final ComboPooledDataSource dataSource = getPrivilegedDataSource(database); + final ComboPooledDataSource dataSource = getDataSource(database); final Connection connection = dataSource.getConnection(); try { /* update query */ @@ -250,8 +220,8 @@ public class SubsetServiceMariaDbImpl extends HibernateConnector implements Subs } @Override - public void deleteStaleQueries(PrivilegedDatabaseDto database) throws SQLException, QueryStoreGCException { - final ComboPooledDataSource dataSource = getPrivilegedDataSource(database); + public void deleteStaleQueries(DatabaseDto database) throws SQLException, QueryStoreGCException { + final ComboPooledDataSource dataSource = getDataSource(database); final Connection connection = dataSource.getConnection(); try { final long start = System.currentTimeMillis(); diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/TableServiceMariaDbImpl.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/TableServiceMariaDbImpl.java index c34f057e01..6586c8ba42 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/TableServiceMariaDbImpl.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/TableServiceMariaDbImpl.java @@ -1,18 +1,16 @@ package at.tuwien.service.impl; import at.tuwien.api.SortTypeDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; +import at.tuwien.api.database.DatabaseDto; import at.tuwien.api.database.query.ImportDto; import at.tuwien.api.database.table.*; import at.tuwien.api.database.table.columns.ColumnDto; import at.tuwien.api.database.table.columns.ColumnStatisticDto; import at.tuwien.api.database.table.columns.ColumnTypeDto; -import at.tuwien.api.database.table.internal.PrivilegedTableDto; -import at.tuwien.api.database.table.internal.TableCreateDto; import at.tuwien.exception.*; import at.tuwien.mapper.DataMapper; import at.tuwien.mapper.MariaDbMapper; -import at.tuwien.service.SchemaService; +import at.tuwien.service.DatabaseService; import at.tuwien.service.StorageService; import at.tuwien.service.TableService; import at.tuwien.utils.MariaDbUtil; @@ -28,66 +26,35 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.time.Instant; -import java.util.*; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Properties; @Log4j2 @Service -public class TableServiceMariaDbImpl extends HibernateConnector implements TableService { +public class TableServiceMariaDbImpl extends DataConnector<TableDto> implements TableService { private final DataMapper dataMapper; private final SparkSession sparkSession; private final MariaDbMapper mariaDbMapper; - private final SchemaService schemaService; private final StorageService storageService; + private final DatabaseService databaseService; @Autowired public TableServiceMariaDbImpl(DataMapper dataMapper, SparkSession sparkSession, MariaDbMapper mariaDbMapper, - SchemaService schemaService, StorageService storageService) { + StorageService storageService, DatabaseService databaseService) { this.dataMapper = dataMapper; this.sparkSession = sparkSession; this.mariaDbMapper = mariaDbMapper; - this.schemaService = schemaService; this.storageService = storageService; + this.databaseService = databaseService; } @Override - public List<TableDto> getSchemas(PrivilegedDatabaseDto database) throws SQLException, TableNotFoundException, - DatabaseMalformedException { - final ComboPooledDataSource dataSource = getPrivilegedDataSource(database); - final Connection connection = dataSource.getConnection(); - final List<TableDto> tables = new LinkedList<>(); - try { - /* inspect tables before views */ - final long start = System.currentTimeMillis(); - final PreparedStatement statement = connection.prepareStatement(mariaDbMapper.databaseTablesSelectRawQuery()); - statement.setString(1, database.getInternalName()); - final ResultSet resultSet1 = statement.executeQuery(); - log.trace("executed statement in {} ms", System.currentTimeMillis() - start); - while (resultSet1.next()) { - final String tableName = resultSet1.getString(1); - if (database.getTables().stream().anyMatch(t -> t.getInternalName().equals(tableName))) { - log.trace("view {}.{} already known to metadata database, skip.", database.getInternalName(), tableName); - continue; - } - final TableDto table = schemaService.inspectTable(database, tableName); - if (database.getTables().stream().noneMatch(t -> t.getInternalName().equals(table.getInternalName()))) { - tables.add(table); - } - } - } catch (SQLException e) { - log.error("Failed to get table schemas: {}", e.getMessage()); - throw new DatabaseMalformedException("Failed to get table schemas: " + e.getMessage(), e); - } finally { - dataSource.close(); - } - log.info("Found {} table schema(s)", tables.size()); - return tables; - } - - @Override - public TableStatisticDto getStatistics(PrivilegedTableDto table) throws SQLException, TableMalformedException, + public TableStatisticDto getStatistics(DatabaseDto database, TableDto table) throws SQLException, TableMalformedException, TableNotFoundException { - final ComboPooledDataSource dataSource = getPrivilegedDataSource(table.getDatabase()); + final ComboPooledDataSource dataSource = getDataSource(table); final Connection connection = dataSource.getConnection(); final TableStatisticDto statistic; try { @@ -95,14 +62,14 @@ public class TableServiceMariaDbImpl extends HibernateConnector implements Table final long start = System.currentTimeMillis(); final String query = mariaDbMapper.tableColumnStatisticsSelectRawQuery(table.getColumns(), table.getInternalName()); if (query == null) { - log.debug("table {}.{} does not have columns that can be analysed for statistical properties (i.e. no numeric columns)", table.getDatabase().getInternalName(), table.getInternalName()); + log.debug("table {}.{} does not have columns that can be analysed for statistical properties (i.e. no numeric columns)", database.getInternalName(), table.getInternalName()); statistic = null; } else { final ResultSet resultSet = connection.prepareStatement(query) .executeQuery(); log.trace("executed statement in {} ms", System.currentTimeMillis() - start); statistic = dataMapper.resultSetToTableStatistic(resultSet); - final TableDto tmpTable = schemaService.inspectTable(table.getDatabase(), table.getInternalName()); + final TableDto tmpTable = databaseService.inspectTable(database, table.getInternalName()); statistic.setAvgRowLength(tmpTable.getAvgRowLength()); statistic.setDataLength(tmpTable.getDataLength()); statistic.setMaxDataLength(tmpTable.getMaxDataLength()); @@ -126,44 +93,9 @@ public class TableServiceMariaDbImpl extends HibernateConnector implements Table } @Override - public TableDto find(PrivilegedDatabaseDto database, String tableName) throws TableNotFoundException, SQLException { - return schemaService.inspectTable(database, tableName); - } - - @Override - public TableDto createTable(PrivilegedDatabaseDto database, TableCreateDto data) throws SQLException, - TableMalformedException, TableExistsException, TableNotFoundException { - final String tableName = mariaDbMapper.nameToInternalName(data.getName()); - final ComboPooledDataSource dataSource = getPrivilegedDataSource(database); - final Connection connection = dataSource.getConnection(); - try { - /* create table if not exists */ - final long start = System.currentTimeMillis(); - connection.prepareStatement(mariaDbMapper.tableCreateDtoToCreateTableRawQuery(data)) - .execute(); - log.trace("executed statement in {} ms", System.currentTimeMillis() - start); - connection.commit(); - } catch (SQLException e) { - connection.rollback(); - if (e.getMessage().contains("already exists")) { - log.error("Failed to create table: already exists"); - throw new TableExistsException("Failed to create table: already exists", e); - } - log.error("Failed to create table: {}", e.getMessage()); - throw new TableMalformedException("Failed to create table: " + e.getMessage(), e); - } finally { - dataSource.close(); - } - log.info("Created table with name {}", tableName); - final TableDto table = find(database, tableName); - table.setName(data.getName()); - return table; - } - - @Override - public void updateTable(PrivilegedTableDto table, TableUpdateDto data) throws SQLException, + public void updateTable(TableDto table, TableUpdateDto data) throws SQLException, TableMalformedException { - final ComboPooledDataSource dataSource = getPrivilegedDataSource(table.getDatabase()); + final ComboPooledDataSource dataSource = getDataSource(table); final Connection connection = dataSource.getConnection(); try { /* create table if not exists */ @@ -189,14 +121,13 @@ public class TableServiceMariaDbImpl extends HibernateConnector implements Table } @Override - public void delete(PrivilegedTableDto table) throws SQLException, QueryMalformedException { - final ComboPooledDataSource dataSource = getPrivilegedDataSource(table.getDatabase()); - final String tableName = mariaDbMapper.nameToInternalName(table.getInternalName()); + public void delete(TableDto table) throws SQLException, QueryMalformedException { + final ComboPooledDataSource dataSource = getDataSource(table); final Connection connection = dataSource.getConnection(); try { /* create table if not exists */ final long start = System.currentTimeMillis(); - connection.prepareStatement(mariaDbMapper.dropTableRawQuery(tableName)) + connection.prepareStatement(mariaDbMapper.dropTableRawQuery(table.getInternalName())) .execute(); log.trace("executed statement in {} ms", System.currentTimeMillis() - start); connection.commit(); @@ -207,63 +138,63 @@ public class TableServiceMariaDbImpl extends HibernateConnector implements Table } finally { dataSource.close(); } - log.info("Deleted table with name {}", tableName); + log.info("Deleted table with name {}", table.getInternalName()); } @Override - public List<TableHistoryDto> history(PrivilegedTableDto table, Long size) throws SQLException, + public List<TableHistoryDto> history(TableDto table, Long size) throws SQLException, TableNotFoundException { - final ComboPooledDataSource dataSource = getPrivilegedDataSource(table.getDatabase()); + final ComboPooledDataSource dataSource = getDataSource(table); final Connection connection = dataSource.getConnection(); final List<TableHistoryDto> history; try { /* find table data */ final long start = System.currentTimeMillis(); final ResultSet resultSet = connection.prepareStatement(mariaDbMapper.selectHistoryRawQuery( - table.getDatabase().getInternalName(), table.getInternalName(), size)) + table.getDatabase(), table.getInternalName(), size)) .executeQuery(); log.trace("executed statement in {} ms", System.currentTimeMillis() - start); history = dataMapper.resultSetToTableHistory(resultSet); connection.commit(); } catch (SQLException e) { connection.rollback(); - log.error("Failed to find history for table {}.{}: {}", table.getDatabase().getInternalName(), table.getInternalName(), e.getMessage()); - throw new TableNotFoundException("Failed to find history for table " + table.getDatabase().getInternalName() + "." + table.getInternalName() + ": " + e.getMessage(), e); + log.error("Failed to find history for table {}.{}: {}", table.getDatabase(), table.getInternalName(), e.getMessage()); + throw new TableNotFoundException("Failed to find history for table " + table.getDatabase() + "." + table.getInternalName() + ": " + e.getMessage(), e); } finally { dataSource.close(); } - log.info("Find history for table {}.{}", table.getDatabase().getInternalName(), table.getInternalName()); + log.info("Find history for table {}.{}", table.getDatabase(), table.getInternalName()); return history; } @Override - public Long getCount(PrivilegedTableDto table, Instant timestamp) throws SQLException, + public Long getCount(TableDto table, Instant timestamp) throws SQLException, QueryMalformedException { - final ComboPooledDataSource dataSource = getPrivilegedDataSource(table.getDatabase()); + final ComboPooledDataSource dataSource = getDataSource(table); final Connection connection = dataSource.getConnection(); final Long queryResult; try { /* find table data */ final long start = System.currentTimeMillis(); final ResultSet resultSet = connection.prepareStatement(mariaDbMapper.selectCountRawQuery( - table.getDatabase().getInternalName(), table.getInternalName(), timestamp)) + table.getDatabase(), table.getInternalName(), timestamp)) .executeQuery(); log.trace("executed statement in {} ms", System.currentTimeMillis() - start); queryResult = mariaDbMapper.resultSetToNumber(resultSet); connection.commit(); } catch (SQLException e) { connection.rollback(); - log.error("Failed to find row count from table {}.{}: {}", table.getDatabase().getInternalName(), table.getInternalName(), e.getMessage()); - throw new QueryMalformedException("Failed to find row count from table " + table.getDatabase().getInternalName() + "." + table.getInternalName() + ": " + e.getMessage(), e); + log.error("Failed to find row count from table {}.{}: {}", table.getDatabase(), table.getInternalName(), e.getMessage()); + throw new QueryMalformedException("Failed to find row count from table " + table.getDatabase() + "." + table.getInternalName() + ": " + e.getMessage(), e); } finally { dataSource.close(); } - log.info("Find row count from table {}.{}", table.getDatabase().getInternalName(), table.getInternalName()); + log.info("Find row count from table {}.{}", table.getDatabase(), table.getInternalName()); return queryResult; } @Override - public void importDataset(PrivilegedTableDto table, ImportDto data) throws MalformedException, + public void importDataset(TableDto table, ImportDto data) throws MalformedException, StorageNotFoundException, StorageUnavailableException, SQLException, QueryMalformedException, TableMalformedException { final List<String> columns = table.getColumns() @@ -273,8 +204,8 @@ public class TableServiceMariaDbImpl extends HibernateConnector implements Table final Dataset<Row> dataset = storageService.loadDataset(columns, data.getLocation(), String.valueOf(data.getSeparator()), data.getHeader()); final Properties properties = new Properties(); - properties.setProperty("user", table.getDatabase().getContainer().getUsername()); - properties.setProperty("password", table.getDatabase().getContainer().getPassword()); + properties.setProperty("user", table.getUsername()); + properties.setProperty("password", table.getPassword()); final String temporaryTable = table.getInternalName() + "_tmp"; try { log.trace("import dataset to temporary table: {}", temporaryTable); @@ -282,8 +213,7 @@ public class TableServiceMariaDbImpl extends HibernateConnector implements Table .mode(SaveMode.Overwrite) .option("header", data.getHeader()) .option("inferSchema", "true") - .jdbc(getSparkUrl(table.getDatabase().getContainer(), table.getDatabase().getInternalName()), - temporaryTable, properties); + .jdbc(getSparkUrl(table), temporaryTable, properties); } catch (Exception e) { if (e instanceof AnalysisException exception) { final String message = exception.getSimpleMessage() @@ -295,7 +225,7 @@ public class TableServiceMariaDbImpl extends HibernateConnector implements Table throw new MalformedException("Failed to write dataset: " + e.getMessage()) /* remove throwable on purpose, clutters the output */; } /* import .csv from sidecar to database */ - final ComboPooledDataSource dataSource = getPrivilegedDataSource(table.getDatabase()); + final ComboPooledDataSource dataSource = getDataSource(table); final Connection connection = dataSource.getConnection(); try { /* import tuple */ @@ -314,15 +244,15 @@ public class TableServiceMariaDbImpl extends HibernateConnector implements Table connection.commit(); dataSource.close(); } - log.info("Imported dataset into table: {}.{}", table.getDatabase().getInternalName(), table.getInternalName()); + log.info("Imported dataset into table: {}.{}", table.getDatabase(), table.getInternalName()); } @Override - public void deleteTuple(PrivilegedTableDto table, TupleDeleteDto data) throws SQLException, + public void deleteTuple(TableDto table, TupleDeleteDto data) throws SQLException, TableMalformedException, QueryMalformedException { log.trace("delete tuple: {}", data); /* prepare the statement */ - final ComboPooledDataSource dataSource = getPrivilegedDataSource(table.getDatabase()); + final ComboPooledDataSource dataSource = getDataSource(table); final Connection connection = dataSource.getConnection(); try { /* import tuple */ @@ -344,11 +274,11 @@ public class TableServiceMariaDbImpl extends HibernateConnector implements Table } finally { dataSource.close(); } - log.info("Deleted tuple(s) from table: {}.{}", table.getDatabase().getInternalName(), table.getInternalName()); + log.info("Deleted tuple(s) from table: {}.{}", table.getDatabase(), table.getInternalName()); } @Override - public void createTuple(PrivilegedTableDto table, TupleDto data) throws SQLException, QueryMalformedException, + public void createTuple(TableDto table, TupleDto data) throws SQLException, QueryMalformedException, TableMalformedException, StorageUnavailableException, StorageNotFoundException { log.trace("create tuple: {}", data); /* for each LOB-like data-column, retrieve the bytes and replace the value */ @@ -366,7 +296,7 @@ public class TableServiceMariaDbImpl extends HibernateConnector implements Table .replace(key, blob); } /* prepare the statement */ - final ComboPooledDataSource dataSource = getPrivilegedDataSource(table.getDatabase()); + final ComboPooledDataSource dataSource = getDataSource(table); final Connection connection = dataSource.getConnection(); try { /* create tuple */ @@ -388,15 +318,15 @@ public class TableServiceMariaDbImpl extends HibernateConnector implements Table } finally { dataSource.close(); } - log.info("Created tuple(s) in table: {}.{}", table.getDatabase().getInternalName(), table.getInternalName()); + log.info("Created tuple(s) in table: {}.{}", table.getDatabase(), table.getInternalName()); } @Override - public void updateTuple(PrivilegedTableDto table, TupleUpdateDto data) throws SQLException, + public void updateTuple(TableDto table, TupleUpdateDto data) throws SQLException, QueryMalformedException, TableMalformedException { log.trace("update tuple: {}", data); /* prepare the statement */ - final ComboPooledDataSource dataSource = getPrivilegedDataSource(table.getDatabase()); + final ComboPooledDataSource dataSource = getDataSource(table); final Connection connection = dataSource.getConnection(); try { final int[] idx = new int[]{1}; @@ -424,7 +354,7 @@ public class TableServiceMariaDbImpl extends HibernateConnector implements Table } finally { dataSource.close(); } - log.info("Updated tuple(s) from table: {}.{}", table.getDatabase().getInternalName(), table.getInternalName()); + log.info("Updated tuple(s) from table: {}.{}", table.getDatabase(), table.getInternalName()); } public ColumnTypeDto getColumnType(List<ColumnDto> columns, String name) throws QueryMalformedException { @@ -439,7 +369,7 @@ public class TableServiceMariaDbImpl extends HibernateConnector implements Table } @Override - public Dataset<Row> getData(PrivilegedDatabaseDto database, String tableOrView, Instant timestamp, + public Dataset<Row> getData(DatabaseDto database, String tableOrView, Instant timestamp, Long page, Long size, SortTypeDto sortDirection, String sortColumn) throws QueryMalformedException, TableNotFoundException { try { @@ -447,7 +377,8 @@ public class TableServiceMariaDbImpl extends HibernateConnector implements Table properties.setProperty("user", database.getContainer().getUsername()); properties.setProperty("password", database.getContainer().getPassword()); return sparkSession.read() - .jdbc(getSparkUrl(database.getContainer(), database.getInternalName()), tableOrView, properties); + .jdbc(getSparkUrl(database.getJdbcMethod(), database.getHost(), database.getPassword(), + database.getInternalName()), tableOrView, properties); } catch (Exception e) { if (e instanceof ExtendedAnalysisException exception) { if (exception.getSimpleMessage().contains("TABLE_OR_VIEW_NOT_FOUND")) { diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/ViewServiceMariaDbImpl.java b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/ViewServiceMariaDbImpl.java index d85bdc53ac..3224c371a3 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/ViewServiceMariaDbImpl.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/service/impl/ViewServiceMariaDbImpl.java @@ -1,182 +1,39 @@ package at.tuwien.service.impl; -import at.tuwien.api.database.ViewCreateDto; import at.tuwien.api.database.ViewDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; -import at.tuwien.api.database.internal.PrivilegedViewDto; -import at.tuwien.api.database.query.QueryDto; -import at.tuwien.config.QueryConfig; -import at.tuwien.exception.DatabaseMalformedException; import at.tuwien.exception.QueryMalformedException; import at.tuwien.exception.ViewMalformedException; -import at.tuwien.exception.ViewNotFoundException; -import at.tuwien.mapper.DataMapper; import at.tuwien.mapper.MariaDbMapper; -import at.tuwien.mapper.MetadataMapper; -import at.tuwien.service.SchemaService; import at.tuwien.service.ViewService; -import com.google.common.hash.Hashing; import com.mchange.v2.c3p0.ComboPooledDataSource; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import java.nio.charset.StandardCharsets; import java.sql.Connection; -import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.time.Instant; -import java.util.LinkedList; -import java.util.List; @Log4j2 @Service -public class ViewServiceMariaDbImpl extends HibernateConnector implements ViewService { +public class ViewServiceMariaDbImpl extends DataConnector<ViewDto> implements ViewService { - private final DataMapper dataMapper; - private final QueryConfig queryConfig; - private final SchemaService schemaService; private final MariaDbMapper mariaDbMapper; - private final MetadataMapper metadataMapper; @Autowired - public ViewServiceMariaDbImpl(DataMapper dataMapper, QueryConfig queryConfig, SchemaService schemaService, - MariaDbMapper mariaDbMapper, MetadataMapper metadataMapper) { - this.dataMapper = dataMapper; - this.queryConfig = queryConfig; - this.schemaService = schemaService; + public ViewServiceMariaDbImpl(MariaDbMapper mariaDbMapper) { this.mariaDbMapper = mariaDbMapper; - this.metadataMapper = metadataMapper; } @Override - public Boolean existsByName(PrivilegedDatabaseDto database, String name) throws SQLException, - QueryMalformedException { - final ComboPooledDataSource dataSource = getPrivilegedDataSource(database); - final Connection connection = dataSource.getConnection(); - final Boolean queryResult; - try { - /* find view data */ - final long start = System.currentTimeMillis(); - final PreparedStatement statement = connection.prepareStatement(mariaDbMapper.selectExistsTableOrViewRawQuery()); - statement.setString(1, database.getInternalName()); - statement.setString(2, name); - final ResultSet resultSet = statement.executeQuery(); - log.trace("executed statement in {} ms", System.currentTimeMillis() - start); - queryResult = mariaDbMapper.resultSetToBoolean(resultSet); - } catch (SQLException e) { - log.error("Failed to prepare statement {}", e.getMessage()); - throw new QueryMalformedException("Failed to prepare statement: " + e.getMessage(), e); - } finally { - dataSource.close(); - } - return queryResult; - } - - @Override - public List<ViewDto> getSchemas(PrivilegedDatabaseDto database) throws SQLException, DatabaseMalformedException, - ViewNotFoundException { - final ComboPooledDataSource dataSource = getPrivilegedDataSource(database); - final Connection connection = dataSource.getConnection(); - final List<ViewDto> views = new LinkedList<>(); - try { - /* inspect tables before views */ - final PreparedStatement statement = connection.prepareStatement(mariaDbMapper.databaseViewsSelectRawQuery()); - statement.setString(1, database.getInternalName()); - final long start = System.currentTimeMillis(); - final ResultSet resultSet1 = statement.executeQuery(); - log.trace("executed statement in {} ms", System.currentTimeMillis() - start); - while (resultSet1.next()) { - final String viewName = resultSet1.getString(1); - if (viewName.length() == 64) { - log.trace("view {}.{} seems to be a subset view (name length = 64), skip.", database.getInternalName(), viewName); - continue; - } - if (database.getViews().stream().anyMatch(v -> v.getInternalName().equals(viewName))) { - log.trace("view {}.{} already known to metadata database, skip.", database.getInternalName(), viewName); - continue; - } - final ViewDto view; - view = schemaService.inspectView(database, viewName); - if (database.getTables().stream().noneMatch(t -> t.getInternalName().equals(view.getInternalName()))) { - views.add(view); - } - } - } catch (SQLException e) { - log.error("Failed to get view schemas: {}", e.getMessage()); - throw new DatabaseMalformedException("Failed to get view schemas: " + e.getMessage(), e); - } finally { - dataSource.close(); - } - log.info("Found {} view schema(s)", views.size()); - return views; - } - - @Override - public ViewDto create(PrivilegedDatabaseDto database, QueryDto subset) throws ViewMalformedException, - SQLException { - final ViewCreateDto data = ViewCreateDto.builder() - .name(metadataMapper.queryDtoToViewName(subset)) - .query(subset.getQuery()) - .isPublic(false) - .build(); - return create(database, data); - } - - @Override - public ViewDto create(PrivilegedDatabaseDto database, ViewCreateDto data) throws SQLException, - ViewMalformedException { - final ComboPooledDataSource dataSource = getPrivilegedDataSource(database); - final Connection connection = dataSource.getConnection(); - ViewDto view = ViewDto.builder() - .name(data.getName()) - .internalName(mariaDbMapper.nameToInternalName(data.getName())) - .query(data.getQuery()) - .queryHash(Hashing.sha256() - .hashString(data.getQuery(), StandardCharsets.UTF_8) - .toString()) - .isPublic(database.getIsPublic()) - .owner(database.getOwner()) - .identifiers(new LinkedList<>()) - .isInitialView(false) - .vdbid(database.getId()) - .columns(new LinkedList<>()) - .build(); - try { - /* create view if not exists */ - final long start = System.currentTimeMillis(); - connection.prepareStatement(mariaDbMapper.viewCreateRawQuery(view.getInternalName(), data.getQuery())) - .execute(); - log.trace("executed statement in {} ms", System.currentTimeMillis() - start); - /* select view columns */ - final PreparedStatement statement2 = connection.prepareStatement(mariaDbMapper.databaseTableColumnsSelectRawQuery()); - statement2.setString(1, database.getInternalName()); - statement2.setString(2, view.getInternalName()); - final ResultSet resultSet2 = statement2.executeQuery(); - while (resultSet2.next()) { - view = dataMapper.resultSetToTable(resultSet2, view, queryConfig); - } - connection.commit(); - } catch (SQLException e) { - connection.rollback(); - log.error("Failed to create view: {}", e.getMessage()); - throw new ViewMalformedException("Failed to create view: " + e.getMessage(), e); - } finally { - dataSource.close(); - } - log.info("Created view with name {}", view.getName()); - return view; - } - - @Override - public void delete(PrivilegedDatabaseDto database, String viewName) throws SQLException, ViewMalformedException { - final ComboPooledDataSource dataSource = getPrivilegedDataSource(database); + public void delete(ViewDto view) throws SQLException, ViewMalformedException { + final ComboPooledDataSource dataSource = getDataSource(view); final Connection connection = dataSource.getConnection(); try { /* drop view if exists */ final long start = System.currentTimeMillis(); - connection.prepareStatement(mariaDbMapper.dropViewRawQuery(viewName)) + connection.prepareStatement(mariaDbMapper.dropViewRawQuery(view.getInternalName())) .execute(); log.trace("executed statement in {} ms", System.currentTimeMillis() - start); connection.commit(); @@ -187,33 +44,32 @@ public class ViewServiceMariaDbImpl extends HibernateConnector implements ViewSe } finally { dataSource.close(); } - log.info("Deleted view {}.{}", database.getInternalName(), viewName); + log.info("Deleted view {}.{}", view.getDatabase(), view.getInternalName()); } - @Override - public Long count(PrivilegedViewDto view, Instant timestamp) throws SQLException, + public Long count(ViewDto view, Instant timestamp) throws SQLException, QueryMalformedException { - final ComboPooledDataSource dataSource = getPrivilegedDataSource(view.getDatabase()); + final ComboPooledDataSource dataSource = getDataSource(view); final Connection connection = dataSource.getConnection(); final Long queryResult; try { /* find view data */ final long start = System.currentTimeMillis(); final ResultSet resultSet = connection.prepareStatement(mariaDbMapper.selectCountRawQuery( - view.getDatabase().getInternalName(), view.getInternalName(), timestamp)) + view.getDatabase(), view.getInternalName(), timestamp)) .executeQuery(); log.trace("executed statement in {} ms", System.currentTimeMillis() - start); queryResult = mariaDbMapper.resultSetToNumber(resultSet); connection.commit(); } catch (SQLException e) { connection.rollback(); - log.error("Failed to find row count from view {}.{}: {}", view.getDatabase().getInternalName(), view.getInternalName(), e.getMessage()); - throw new QueryMalformedException("Failed to find row count from view " + view.getDatabase().getInternalName() + "." + view.getInternalName() + ": " + e.getMessage(), e); + log.error("Failed to find row count from view {}.{}: {}", view.getDatabase(), view.getInternalName(), e.getMessage()); + throw new QueryMalformedException("Failed to find row count from view " + view.getDatabase() + "." + view.getInternalName() + ": " + e.getMessage(), e); } finally { dataSource.close(); } - log.info("Find row count from view {}.{}", view.getDatabase().getInternalName(), view.getInternalName()); + log.info("Find row count from view {}.{}", view.getDatabase(), view.getInternalName()); return queryResult; } diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/CacheableDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/CacheableDto.java new file mode 100644 index 0000000000..66de637504 --- /dev/null +++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/CacheableDto.java @@ -0,0 +1,45 @@ +package at.tuwien.api; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import java.time.Instant; + +@Getter +@Setter +@EqualsAndHashCode +@ToString +public abstract class CacheableDto { + + @JsonProperty("last_retrieved") + private Instant lastRetrieved; + + @ToString.Exclude + @JsonIgnore + private String jdbcMethod; + + @ToString.Exclude + @JsonIgnore + private String host; + + @ToString.Exclude + @JsonIgnore + private Integer port; + + @ToString.Exclude + @JsonIgnore + private String username; + + @ToString.Exclude + @JsonIgnore + private String password; + + @ToString.Exclude + @JsonIgnore + private String database; + +} diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/PrivilegedObjectDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/PrivilegedObjectDto.java deleted file mode 100644 index c88fcabccf..0000000000 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/PrivilegedObjectDto.java +++ /dev/null @@ -1,18 +0,0 @@ -package at.tuwien.api; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; - -import java.time.Instant; - -@Getter -@Setter -@ToString -public abstract class PrivilegedObjectDto { - - @JsonProperty("last_retrieved") - private Instant lastRetrieved; - -} diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/ContainerDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/ContainerDto.java index 9928c8e54d..da782c2bb7 100644 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/ContainerDto.java +++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/ContainerDto.java @@ -1,7 +1,9 @@ package at.tuwien.api.container; +import at.tuwien.api.CacheableDto; import at.tuwien.api.container.image.ImageDto; import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; @@ -18,7 +20,7 @@ import java.time.Instant; @AllArgsConstructor @Jacksonized @ToString -public class ContainerDto { +public class ContainerDto extends CacheableDto { @NotNull private Long id; @@ -55,4 +57,25 @@ public class ContainerDto { @Schema(example = "10") private Long count; + /* lombok limitations prevent from convenient builder functions */ + + @JsonProperty("last_retrieved") + private Instant lastRetrieved; + + @ToString.Exclude + @JsonIgnore + private String jdbcMethod; + + @ToString.Exclude + @JsonIgnore + private String username; + + @ToString.Exclude + @JsonIgnore + private String password; + + @ToString.Exclude + @JsonIgnore + private String database; + } diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/internal/PrivilegedContainerDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/internal/PrivilegedContainerDto.java deleted file mode 100644 index 9c414e5ef1..0000000000 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/container/internal/PrivilegedContainerDto.java +++ /dev/null @@ -1,60 +0,0 @@ -package at.tuwien.api.container.internal; - -import at.tuwien.api.PrivilegedObjectDto; -import at.tuwien.api.container.image.ImageDto; -import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.*; -import lombok.extern.jackson.Jacksonized; - -import java.time.Instant; - -@Getter -@Setter -@Builder -@NoArgsConstructor -@AllArgsConstructor -@Jacksonized -@ToString -public class PrivilegedContainerDto extends PrivilegedObjectDto { - - @NotNull - private Long id; - - @NotBlank - @Schema(example = "Air Quality") - private String name; - - @NotBlank - @JsonProperty("internal_name") - @Schema(example = "data-db") - private String internalName; - - @NotBlank - private String host; - - @NotNull - private Integer port; - - @JsonProperty("ui_host") - private String uiHost; - - @JsonProperty("ui_port") - private Integer uiPort; - - @NotNull - private ImageDto image; - - @ToString.Exclude - private String username; - - @ToString.Exclude - private String password; - - @JsonProperty("last_retrieved") - private Instant lastRetrieved; - -} diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/DatabaseBriefDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/DatabaseBriefDto.java index 46072e83dc..7b47a617ec 100644 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/DatabaseBriefDto.java +++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/DatabaseBriefDto.java @@ -56,4 +56,7 @@ public class DatabaseBriefDto { @JsonProperty("owner_id") private UUID ownerId; + @JsonProperty("preview_image") + private String previewImage; + } diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/DatabaseDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/DatabaseDto.java index 5fc253c433..044e52df38 100644 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/DatabaseDto.java +++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/DatabaseDto.java @@ -1,9 +1,11 @@ package at.tuwien.api.database; -import at.tuwien.api.container.ContainerBriefDto; -import at.tuwien.api.database.table.TableBriefDto; -import at.tuwien.api.identifier.IdentifierBriefDto; +import at.tuwien.api.CacheableDto; +import at.tuwien.api.container.ContainerDto; +import at.tuwien.api.database.table.TableDto; +import at.tuwien.api.identifier.IdentifierDto; import at.tuwien.api.user.UserBriefDto; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; @@ -11,17 +13,18 @@ import jakarta.validation.constraints.NotNull; import lombok.*; import lombok.extern.jackson.Jacksonized; +import java.time.Instant; import java.util.List; @Getter @Setter @Builder -@EqualsAndHashCode +@EqualsAndHashCode(callSuper = true) @NoArgsConstructor @AllArgsConstructor @Jacksonized @ToString -public class DatabaseDto { +public class DatabaseDto extends CacheableDto { @NotNull private Long id; @@ -47,9 +50,9 @@ public class DatabaseDto { @Schema(example = "Air Quality") private String description; - private List<TableBriefDto> tables; + private List<TableDto> tables; - private List<ViewBriefDto> views; + private List<ViewDto> views; @NotNull @JsonProperty("is_public") @@ -62,13 +65,13 @@ public class DatabaseDto { private Boolean isSchemaPublic; @NotNull - private ContainerBriefDto container; + private ContainerDto container; private List<DatabaseAccessDto> accesses; - private List<IdentifierBriefDto> identifiers; + private List<IdentifierDto> identifiers; - private List<IdentifierBriefDto> subsets; + private List<IdentifierDto> subsets; @NotNull private UserBriefDto contact; @@ -79,4 +82,33 @@ public class DatabaseDto { @JsonProperty("preview_image") private String previewImage; + /* lombok limitations prevent from convenient builder functions */ + + @JsonProperty("last_retrieved") + private Instant lastRetrieved; + + @ToString.Exclude + @JsonIgnore + private String jdbcMethod; + + @ToString.Exclude + @JsonIgnore + private String host; + + @ToString.Exclude + @JsonIgnore + private Integer port; + + @ToString.Exclude + @JsonIgnore + private String username; + + @ToString.Exclude + @JsonIgnore + private String password; + + @ToString.Exclude + @JsonIgnore + private String database; + } diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/ViewDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/ViewDto.java index d1ee156e9b..13e64911f5 100644 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/ViewDto.java +++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/ViewDto.java @@ -1,7 +1,9 @@ package at.tuwien.api.database; +import at.tuwien.api.CacheableDto; import at.tuwien.api.identifier.IdentifierDto; import at.tuwien.api.user.UserBriefDto; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; @@ -9,17 +11,18 @@ import jakarta.validation.constraints.NotNull; import lombok.*; import lombok.extern.jackson.Jacksonized; +import java.time.Instant; import java.util.List; @Getter @Setter @Builder -@EqualsAndHashCode +@EqualsAndHashCode(callSuper = true) @NoArgsConstructor @AllArgsConstructor @Jacksonized @ToString -public class ViewDto { +public class ViewDto extends CacheableDto { @NotNull private Long id; @@ -66,4 +69,33 @@ public class ViewDto { @NotNull private List<ViewColumnDto> columns; + /* lombok limitations prevent from convenient builder functions */ + + @JsonProperty("last_retrieved") + private Instant lastRetrieved; + + @ToString.Exclude + @JsonIgnore + private String jdbcMethod; + + @ToString.Exclude + @JsonIgnore + private String host; + + @ToString.Exclude + @JsonIgnore + private Integer port; + + @ToString.Exclude + @JsonIgnore + private String username; + + @ToString.Exclude + @JsonIgnore + private String password; + + @ToString.Exclude + @JsonIgnore + private String database; + } diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/internal/PrivilegedDatabaseDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/internal/PrivilegedDatabaseDto.java deleted file mode 100644 index 2335ea39ba..0000000000 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/internal/PrivilegedDatabaseDto.java +++ /dev/null @@ -1,88 +0,0 @@ -package at.tuwien.api.database.internal; - -import at.tuwien.api.PrivilegedObjectDto; -import at.tuwien.api.container.internal.PrivilegedContainerDto; -import at.tuwien.api.database.DatabaseAccessDto; -import at.tuwien.api.database.ViewDto; -import at.tuwien.api.database.table.TableDto; -import at.tuwien.api.identifier.IdentifierDto; -import at.tuwien.api.user.UserBriefDto; -import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.*; -import lombok.extern.jackson.Jacksonized; - -import java.time.Instant; -import java.util.List; - -@Getter -@Setter -@Builder -@NoArgsConstructor -@AllArgsConstructor -@Jacksonized -@ToString -public class PrivilegedDatabaseDto extends PrivilegedObjectDto { - - @NotNull - private Long id; - - @NotBlank - @Schema(example = "Air Quality") - private String name; - - @NotBlank - @JsonProperty("exchange_name") - @Schema(example = "dbrepo") - private String exchangeName; - - @JsonProperty("exchange_type") - @Schema(example = "topic") - private String exchangeType; - - @NotBlank - @JsonProperty("internal_name") - @Schema(example = "air_quality") - private String internalName; - - @Schema(example = "Air Quality") - private String description; - - private List<TableDto> tables; - - private List<ViewDto> views; - - @NotNull - @JsonProperty("is_public") - @Schema(example = "true") - private Boolean isPublic; - - @NotNull - @JsonProperty("is_schema_public") - @Schema(example = "true") - private Boolean isSchemaPublic; - - @NotNull - private PrivilegedContainerDto container; - - private List<DatabaseAccessDto> accesses; - - private List<IdentifierDto> identifiers; - - private List<IdentifierDto> subsets; - - @NotNull - private UserBriefDto contact; - - @NotNull - private UserBriefDto owner; - - @JsonProperty("preview_image") - private String previewImage; - - @JsonProperty("last_retrieved") - private Instant lastRetrieved; - -} diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/internal/PrivilegedViewDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/internal/PrivilegedViewDto.java deleted file mode 100644 index bda575f45d..0000000000 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/internal/PrivilegedViewDto.java +++ /dev/null @@ -1,77 +0,0 @@ -package at.tuwien.api.database.internal; - -import at.tuwien.api.PrivilegedObjectDto; -import at.tuwien.api.database.ViewColumnDto; -import at.tuwien.api.identifier.IdentifierDto; -import at.tuwien.api.user.UserBriefDto; -import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.*; -import lombok.extern.jackson.Jacksonized; - -import java.time.Instant; -import java.util.List; - -@Getter -@Setter -@Builder -@NoArgsConstructor -@AllArgsConstructor -@Jacksonized -@ToString -public class PrivilegedViewDto extends PrivilegedObjectDto { - - @NotNull - private Long id; - - @NotNull - @JsonProperty("database_id") - private Long vdbid; - - @NotNull - private PrivilegedDatabaseDto database; - - @NotBlank - @Schema(example = "Air Quality") - private String name; - - private List<IdentifierDto> identifiers; - - @NotBlank - @Schema(example = "air_quality") - @JsonProperty("internal_name") - private String internalName; - - @JsonProperty("is_public") - @Schema(example = "true") - private Boolean isPublic; - - @JsonProperty("is_schema_public") - @Schema(example = "true") - private Boolean isSchemaPublic; - - @JsonProperty("initial_view") - @Schema(example = "true", description = "True if it is the default view for the database") - private Boolean isInitialView; - - @NotNull - @Schema(example = "SELECT `id` FROM `air_quality` ORDER BY `value` DESC") - private String query; - - @NotNull - @JsonProperty("query_hash") - @Schema(example = "7de03e818900b6ea6d58ad0306d4a741d658c6df3d1964e89ed2395d8c7e7916") - private String queryHash; - - @NotNull - private UserBriefDto owner; - - @NotNull - private List<ViewColumnDto> columns; - - @JsonProperty("last_retrieved") - private Instant lastRetrieved; - -} diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/table/TableDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/table/TableDto.java index 67087d438d..cf18e321a4 100644 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/table/TableDto.java +++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/table/TableDto.java @@ -1,9 +1,11 @@ package at.tuwien.api.database.table; +import at.tuwien.api.CacheableDto; import at.tuwien.api.database.table.columns.ColumnDto; import at.tuwien.api.database.table.constraints.ConstraintsDto; import at.tuwien.api.identifier.IdentifierDto; import at.tuwien.api.user.UserBriefDto; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; @@ -12,17 +14,18 @@ import jakarta.validation.constraints.Size; import lombok.*; import lombok.extern.jackson.Jacksonized; +import java.time.Instant; import java.util.List; @Getter @Setter @Builder -@EqualsAndHashCode +@EqualsAndHashCode(callSuper = true) @NoArgsConstructor @AllArgsConstructor @Jacksonized @ToString -public class TableDto { +public class TableDto extends CacheableDto { @NotNull private Long id; @@ -103,4 +106,33 @@ public class TableDto { @NotNull private ConstraintsDto constraints; + /* lombok limitations prevent from convenient builder functions */ + + @JsonProperty("last_retrieved") + private Instant lastRetrieved; + + @ToString.Exclude + @JsonIgnore + private String jdbcMethod; + + @ToString.Exclude + @JsonIgnore + private String host; + + @ToString.Exclude + @JsonIgnore + private Integer port; + + @ToString.Exclude + @JsonIgnore + private String username; + + @ToString.Exclude + @JsonIgnore + private String password; + + @ToString.Exclude + @JsonIgnore + private String database; + } diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/table/internal/PrivilegedTableDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/table/internal/PrivilegedTableDto.java deleted file mode 100644 index 64b23f17c4..0000000000 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/table/internal/PrivilegedTableDto.java +++ /dev/null @@ -1,115 +0,0 @@ -package at.tuwien.api.database.table.internal; - -import at.tuwien.api.PrivilegedObjectDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; -import at.tuwien.api.database.table.columns.ColumnDto; -import at.tuwien.api.database.table.constraints.ConstraintsDto; -import at.tuwien.api.identifier.IdentifierDto; -import at.tuwien.api.user.UserBriefDto; -import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; -import lombok.*; -import lombok.extern.jackson.Jacksonized; - -import java.time.Instant; -import java.util.List; - -@Getter -@Setter -@Builder -@NoArgsConstructor -@AllArgsConstructor -@Jacksonized -@ToString -@EqualsAndHashCode -public class PrivilegedTableDto extends PrivilegedObjectDto { - - @NotNull - private Long id; - - @NotNull - @JsonProperty("database_id") - private Long tdbid; - - @NotBlank - @Schema(example = "Air Quality") - private String name; - - @NotBlank - @JsonProperty("internal_name") - @Schema(example = "air_quality") - private String internalName; - - @Schema - private String alias; - - private List<IdentifierDto> identifiers; - - @NotNull - @JsonProperty("is_versioned") - @Schema(example = "true") - private Boolean isVersioned; - - @NotNull - @JsonProperty("is_schema_public") - @Schema(example = "true") - private Boolean isSchemaPublic; - - @NotNull - private UserBriefDto owner; - - @NotBlank - @JsonProperty("queue_name") - @Schema(example = "air_quality") - private String queueName; - - @JsonProperty("queue_type") - @Schema(example = "quorum") - private String queueType; - - @NotBlank - @JsonProperty("routing_key") - @Schema(example = "dbrepo.1.2") - private String routingKey; - - @Size(max = 2048) - @Schema(example = "Air Quality in Austria") - private String description; - - @NotNull - @JsonProperty("is_public") - @Schema(example = "true") - private Boolean isPublic; - - @JsonProperty("num_rows") - @Schema(example = "5") - private Long numRows; - - @JsonProperty("data_length") - @Schema(example = "16384", description = "in bytes") - private Long dataLength; - - @JsonProperty("max_data_length") - @Schema(example = "0", description = "in bytes") - private Long maxDataLength; - - @JsonProperty("avg_row_length") - @Schema(example = "3276", description = "in bytes") - private Long avgRowLength; - - @NotNull - private List<ColumnDto> columns; - - @NotNull - private ConstraintsDto constraints; - - @NotNull - private PrivilegedDatabaseDto database; - - @JsonProperty("last_retrieved") - private Instant lastRetrieved; - -} diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/identifier/IdentifierBriefDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/identifier/IdentifierBriefDto.java index 0a36c561a3..82ff3b0fe7 100644 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/identifier/IdentifierBriefDto.java +++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/identifier/IdentifierBriefDto.java @@ -1,6 +1,5 @@ package at.tuwien.api.identifier; -import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; @@ -8,7 +7,6 @@ import jakarta.validation.constraints.NotNull; import lombok.*; import lombok.extern.jackson.Jacksonized; -import java.time.Instant; import java.util.List; import java.util.UUID; diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/user/UserDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/user/UserDto.java index 343d582b55..43fb10201b 100644 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/user/UserDto.java +++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/user/UserDto.java @@ -1,22 +1,25 @@ package at.tuwien.api.user; +import at.tuwien.api.CacheableDto; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import lombok.*; import lombok.extern.jackson.Jacksonized; +import java.time.Instant; import java.util.UUID; @Getter @Setter @Builder -@EqualsAndHashCode +@EqualsAndHashCode(callSuper = true) @NoArgsConstructor @AllArgsConstructor @Jacksonized @ToString -public class UserDto { +public class UserDto extends CacheableDto { @NotNull @Schema(example = "1ffc7b0e-9aeb-4e8b-b8f1-68f3936155b4") @@ -41,7 +44,14 @@ public class UserDto { @Schema(example = "Carberry") private String lastname; + @ToString.Exclude + @JsonIgnore + private String password; + @NotNull private UserAttributesDto attributes; + @JsonProperty("last_retrieved") + private Instant lastRetrieved; + } diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/user/internal/PrivilegedUserDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/user/internal/PrivilegedUserDto.java deleted file mode 100644 index 56e24cd815..0000000000 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/user/internal/PrivilegedUserDto.java +++ /dev/null @@ -1,58 +0,0 @@ -package at.tuwien.api.user.internal; - -import at.tuwien.api.PrivilegedObjectDto; -import at.tuwien.api.user.UserAttributesDto; -import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.*; -import lombok.extern.jackson.Jacksonized; - -import java.time.Instant; -import java.util.UUID; - -@Getter -@Setter -@Builder -@EqualsAndHashCode -@NoArgsConstructor -@AllArgsConstructor -@Jacksonized -@ToString -public class PrivilegedUserDto extends PrivilegedObjectDto { - - @NotNull - @Schema(example = "1ffc7b0e-9aeb-4e8b-b8f1-68f3936155b4") - private UUID id; - - @NotBlank - @Schema(example = "jcarberry", description = "Only contains lowercase characters") - private String username; - - @NotBlank - @Schema(example = "jcarberry") - private String password; - - @Schema(example = "Josiah Carberry") - private String name; - - @JsonProperty("qualified_name") - @Schema(example = "Josiah Carberry — @jcarberry") - private String qualifiedName; - - @JsonProperty("given_name") - @Schema(example = "Josiah") - private String firstname; - - @JsonProperty("family_name") - @Schema(example = "Carberry") - private String lastname; - - @NotNull - private UserAttributesDto attributes; - - @JsonProperty("last_retrieved") - private Instant lastRetrieved; - -} diff --git a/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/MetadataMapper.java b/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/MetadataMapper.java index c5482f7041..ab13affe7e 100644 --- a/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/MetadataMapper.java +++ b/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/MetadataMapper.java @@ -10,7 +10,6 @@ import at.tuwien.api.container.image.ImageCreateDto; import at.tuwien.api.container.image.ImageDto; import at.tuwien.api.crossref.CrossrefDto; import at.tuwien.api.database.*; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; import at.tuwien.api.database.table.TableBriefDto; import at.tuwien.api.database.table.TableDto; import at.tuwien.api.database.table.columns.ColumnCreateDto; @@ -120,7 +119,10 @@ public interface MetadataMapper { }) ContainerBriefDto containerToContainerBriefDto(Container data); - PrivilegedDatabaseDto databaseToPrivilegedDatabaseDto(Database data); + @Mappings({ + @Mapping(target = "previewImage", expression = "java(database.getImage() != null ? \"/api/database/\" + database.getId() + \"/image\" : null)") + }) + DatabaseDto databaseToDatabaseDto(Database database); @Mappings({ @Mapping(target = "titles", source = "."), @@ -531,7 +533,7 @@ public interface MetadataMapper { .build(); } - default TableDto customTableToTableDto(Table data) { + default TableDto tableToTableDto(Table data) { final TableDto table = TableDto.builder() .id(data.getId()) .name(data.getName()) @@ -620,7 +622,8 @@ public interface MetadataMapper { Unique uniqueDtoToUnique(UniqueDto data); @Mappings({ - @Mapping(target = "ownedBy", source = "owner.id") + @Mapping(target = "ownedBy", source = "owner.id"), + @Mapping(target = "database", ignore = true) }) Table tableDtoToTable(TableDto data); @@ -822,6 +825,9 @@ public interface MetadataMapper { .trim(); } + @Mappings({ + @Mapping(target = "database", ignore = true) + }) ViewDto viewToViewDto(View data); @Mappings({ @@ -831,6 +837,9 @@ public interface MetadataMapper { ViewBriefDto viewToViewBriefDto(View data); + @Mappings({ + @Mapping(target = "database", ignore = true) + }) View viewDtoToView(ViewDto data); /* keep */ @@ -852,60 +861,7 @@ public interface MetadataMapper { LanguageType languageTypeDtoToLanguageType(LanguageTypeDto data); - default DatabaseDto customDatabaseToDatabaseDto(Database data) { - if (data == null) { - return null; - } - final DatabaseDto database = DatabaseDto.builder() - .id(data.getId()) - .name(data.getName()) - .internalName(data.getInternalName()) - .description(data.getDescription()) - .exchangeName(data.getExchangeName()) - .previewImage(data.getImage() != null ? "/api/database/" + data.getId() + "/image" : null) - .isPublic(data.getIsPublic()) - .isSchemaPublic(data.getIsSchemaPublic()) - .container(containerToContainerBriefDto(data.getContainer())) - .owner(userToUserBriefDto(data.getOwner())) - .contact(userToUserBriefDto(data.getContact())) - .subsets(new LinkedList<>()) - .accesses(new LinkedList<>()) - .tables(new LinkedList<>()) - .identifiers(new LinkedList<>()) - .build(); - if (data.getSubsets() != null) { - database.setSubsets(new LinkedList<>(data.getSubsets() - .stream() - .map(this::identifierToIdentifierBriefDto) - .toList())); - } - if (data.getTables() != null) { - database.setTables(new LinkedList<>(data.getTables() - .stream() - .map(this::tableToTableBriefDto) - .toList())); - } - if (data.getViews() != null) { - database.setViews(new LinkedList<>(data.getViews() - .stream() - .map(this::viewToViewBriefDto) - .toList())); - } - if (data.getAccesses() != null) { - database.setAccesses(new LinkedList<>(data.getAccesses() - .stream() - .filter(a -> !a.getUser().getIsInternal()) - .map(this::databaseAccessToDatabaseAccessDto) - .toList())); - } - if (data.getIdentifiers() != null) { - database.setIdentifiers(new LinkedList<>(data.getIdentifiers() - .stream() - .map(this::identifierToIdentifierBriefDto) - .toList())); - } - return database; - } + DatabaseBriefDto databaseDtoToDatabaseBriefDto(DatabaseDto data); @Mappings({ @Mapping(target = "ownerId", source = "owner.id") diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java index ad72fb0756..7e03202480 100644 --- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java +++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/DatabaseEndpoint.java @@ -5,8 +5,6 @@ import at.tuwien.api.error.ApiErrorDto; import at.tuwien.entities.container.Container; import at.tuwien.entities.database.Database; import at.tuwien.entities.database.DatabaseAccess; -import at.tuwien.entities.database.View; -import at.tuwien.entities.database.table.Table; import at.tuwien.entities.user.User; import at.tuwien.exception.*; import at.tuwien.mapper.MetadataMapper; @@ -73,7 +71,7 @@ public class DatabaseEndpoint extends AbstractEndpoint { @Header(name = "Access-Control-Expose-Headers", description = "Expose `X-Count` custom header", schema = @Schema(implementation = String.class), required = true)}, content = {@Content( mediaType = "application/json", - array = @ArraySchema(schema = @Schema(implementation = DatabaseDto.class)))}), + array = @ArraySchema(schema = @Schema(implementation = DatabaseBriefDto.class)))}), }) public ResponseEntity<List<DatabaseBriefDto>> list(@RequestParam(name = "internal_name", required = false) String internalName, Principal principal) { @@ -118,7 +116,7 @@ public class DatabaseEndpoint extends AbstractEndpoint { description = "Created a new database", content = {@Content( mediaType = "application/json", - schema = @Schema(implementation = DatabaseDto.class))}), + schema = @Schema(implementation = DatabaseBriefDto.class))}), @ApiResponse(responseCode = "400", description = "Database create query is malformed or image is not supported", content = {@Content( @@ -155,8 +153,8 @@ public class DatabaseEndpoint extends AbstractEndpoint { mediaType = "application/json", schema = @Schema(implementation = ApiErrorDto.class))}), }) - public ResponseEntity<DatabaseDto> create(@Valid @RequestBody DatabaseCreateDto data, - @NotNull Principal principal) throws DataServiceException, + public ResponseEntity<DatabaseBriefDto> create(@Valid @RequestBody DatabaseCreateDto data, + @NotNull Principal principal) throws DataServiceException, DataServiceConnectionException, UserNotFoundException, DatabaseNotFoundException, ContainerNotFoundException, SearchServiceException, SearchServiceConnectionException, ContainerQuotaException { @@ -168,8 +166,8 @@ public class DatabaseEndpoint extends AbstractEndpoint { } final User caller = userService.findById(getId(principal)); return ResponseEntity.status(HttpStatus.CREATED) - .body(databaseMapper.customDatabaseToDatabaseDto( - databaseService.create(container, data, caller, userService.findAllInternalUsers()))); + .body(databaseMapper.databaseDtoToDatabaseBriefDto(databaseMapper.databaseToDatabaseDto( + databaseService.create(container, data, caller, userService.findAllInternalUsers())))); } @PutMapping("/{databaseId}/metadata/table") @@ -184,7 +182,7 @@ public class DatabaseEndpoint extends AbstractEndpoint { description = "Refreshed database tables metadata", content = {@Content( mediaType = "application/json", - schema = @Schema(implementation = DatabaseDto.class))}), + schema = @Schema(implementation = DatabaseBriefDto.class))}), @ApiResponse(responseCode = "400", description = "Failed to parse payload at search service", content = {@Content( @@ -211,8 +209,8 @@ public class DatabaseEndpoint extends AbstractEndpoint { mediaType = "application/json", schema = @Schema(implementation = ApiErrorDto.class))}), }) - public ResponseEntity<DatabaseDto> refreshTableMetadata(@NotNull @PathVariable("databaseId") Long databaseId, - @NotNull Principal principal) throws DataServiceException, + public ResponseEntity<DatabaseBriefDto> refreshTableMetadata(@NotNull @PathVariable("databaseId") Long databaseId, + @NotNull Principal principal) throws DataServiceException, DataServiceConnectionException, DatabaseNotFoundException, SearchServiceException, UserNotFoundException, SearchServiceConnectionException, NotAllowedException, QueryNotFoundException, MalformedException, TableNotFoundException { @@ -222,8 +220,8 @@ public class DatabaseEndpoint extends AbstractEndpoint { log.error("Failed to refresh database tables metadata: not owner"); throw new NotAllowedException("Failed to refresh tables metadata: not owner"); } - return ResponseEntity.ok(databaseMapper.customDatabaseToDatabaseDto( - databaseService.updateTableMetadata(database))); + return ResponseEntity.ok(databaseMapper.databaseDtoToDatabaseBriefDto(databaseMapper.databaseToDatabaseDto( + databaseService.updateTableMetadata(database)))); } @PutMapping("/{databaseId}/metadata/view") @@ -238,7 +236,7 @@ public class DatabaseEndpoint extends AbstractEndpoint { description = "Refreshed database views metadata", content = {@Content( mediaType = "application/json", - schema = @Schema(implementation = DatabaseDto.class))}), + schema = @Schema(implementation = DatabaseBriefDto.class))}), @ApiResponse(responseCode = "403", description = "Refresh view metadata is not permitted", content = {@Content( @@ -260,9 +258,9 @@ public class DatabaseEndpoint extends AbstractEndpoint { mediaType = "application/json", schema = @Schema(implementation = ApiErrorDto.class))}), }) - public ResponseEntity<DatabaseDto> refreshViewMetadata(@NotNull @PathVariable("databaseId") Long databaseId, - @NotNull Principal principal) throws DataServiceException, - DataServiceConnectionException, DatabaseNotFoundException, SearchServiceException, UserNotFoundException, + public ResponseEntity<DatabaseBriefDto> refreshViewMetadata(@NotNull @PathVariable("databaseId") Long databaseId, + @NotNull Principal principal) throws DataServiceException, + DataServiceConnectionException, DatabaseNotFoundException, SearchServiceException, SearchServiceConnectionException, NotAllowedException, QueryNotFoundException, ViewNotFoundException { log.debug("endpoint refresh database metadata, databaseId={}, principal.name={}", databaseId, principal.getName()); final Database database = databaseService.findById(databaseId); @@ -270,8 +268,8 @@ public class DatabaseEndpoint extends AbstractEndpoint { log.error("Failed to refresh database views metadata: not owner"); throw new NotAllowedException("Failed to refresh database views metadata: not owner"); } - return ResponseEntity.ok(databaseMapper.customDatabaseToDatabaseDto( - databaseService.updateViewMetadata(database))); + return ResponseEntity.ok(databaseMapper.databaseDtoToDatabaseBriefDto(databaseMapper.databaseToDatabaseDto( + databaseService.updateViewMetadata(database)))); } @PutMapping("/{databaseId}/visibility") @@ -286,7 +284,7 @@ public class DatabaseEndpoint extends AbstractEndpoint { description = "Visibility modified successfully", content = {@Content( mediaType = "application/json", - schema = @Schema(implementation = DatabaseDto.class))}), + schema = @Schema(implementation = DatabaseBriefDto.class))}), @ApiResponse(responseCode = "400", description = "The visibility payload is malformed", content = {@Content( @@ -313,9 +311,9 @@ public class DatabaseEndpoint extends AbstractEndpoint { mediaType = "application/json", schema = @Schema(implementation = ApiErrorDto.class))}), }) - public ResponseEntity<DatabaseDto> visibility(@NotNull @PathVariable("databaseId") Long databaseId, - @Valid @RequestBody DatabaseModifyVisibilityDto data, - @NotNull Principal principal) throws DatabaseNotFoundException, + public ResponseEntity<DatabaseBriefDto> visibility(@NotNull @PathVariable("databaseId") Long databaseId, + @Valid @RequestBody DatabaseModifyVisibilityDto data, + @NotNull Principal principal) throws DatabaseNotFoundException, NotAllowedException, SearchServiceException, SearchServiceConnectionException, UserNotFoundException { log.debug("endpoint modify database visibility, databaseId={}, data={}", databaseId, data); final Database database = databaseService.findById(databaseId); @@ -324,8 +322,8 @@ public class DatabaseEndpoint extends AbstractEndpoint { throw new NotAllowedException("Failed to modify database visibility: not owner"); } return ResponseEntity.accepted() - .body(databaseMapper.customDatabaseToDatabaseDto( - databaseService.modifyVisibility(database, data))); + .body(databaseMapper.databaseDtoToDatabaseBriefDto(databaseMapper.databaseToDatabaseDto( + databaseService.modifyVisibility(database, data)))); } @PutMapping("/{databaseId}/owner") @@ -340,7 +338,7 @@ public class DatabaseEndpoint extends AbstractEndpoint { description = "Transfer of ownership was successful", content = {@Content( mediaType = "application/json", - schema = @Schema(implementation = DatabaseDto.class))}), + schema = @Schema(implementation = DatabaseBriefDto.class))}), @ApiResponse(responseCode = "400", description = "Owner payload is malformed", content = {@Content( @@ -367,9 +365,9 @@ public class DatabaseEndpoint extends AbstractEndpoint { mediaType = "application/json", schema = @Schema(implementation = ApiErrorDto.class))}), }) - public ResponseEntity<DatabaseDto> transfer(@NotNull @PathVariable("databaseId") Long databaseId, - @Valid @RequestBody DatabaseTransferDto data, - @NotNull Principal principal) throws NotAllowedException, + public ResponseEntity<DatabaseBriefDto> transfer(@NotNull @PathVariable("databaseId") Long databaseId, + @Valid @RequestBody DatabaseTransferDto data, + @NotNull Principal principal) throws NotAllowedException, DataServiceException, DataServiceConnectionException, DatabaseNotFoundException, UserNotFoundException, SearchServiceException, SearchServiceConnectionException { log.debug("endpoint transfer database, databaseId={}, transferDto.id={}", databaseId, data.getId()); @@ -380,8 +378,8 @@ public class DatabaseEndpoint extends AbstractEndpoint { throw new NotAllowedException("Failed to transfer database: not owner"); } return ResponseEntity.accepted() - .body(databaseMapper.customDatabaseToDatabaseDto( - databaseService.modifyOwner(database, newOwner))); + .body(databaseMapper.databaseDtoToDatabaseBriefDto(databaseMapper.databaseToDatabaseDto( + databaseService.modifyOwner(database, newOwner)))); } @PutMapping("/{databaseId}/image") @@ -396,7 +394,7 @@ public class DatabaseEndpoint extends AbstractEndpoint { description = "Modify of image was successful", content = {@Content( mediaType = "application/json", - schema = @Schema(implementation = DatabaseDto.class))}), + schema = @Schema(implementation = DatabaseBriefDto.class))}), @ApiResponse(responseCode = "403", description = "Modify of image is not permitted", content = {@Content( @@ -423,9 +421,9 @@ public class DatabaseEndpoint extends AbstractEndpoint { mediaType = "application/json", schema = @Schema(implementation = ApiErrorDto.class))}), }) - public ResponseEntity<DatabaseDto> modifyImage(@NotNull @PathVariable("databaseId") Long databaseId, - @Valid @RequestBody DatabaseModifyImageDto data, - @NotNull Principal principal) throws NotAllowedException, + public ResponseEntity<DatabaseBriefDto> modifyImage(@NotNull @PathVariable("databaseId") Long databaseId, + @Valid @RequestBody DatabaseModifyImageDto data, + @NotNull Principal principal) throws NotAllowedException, DatabaseNotFoundException, SearchServiceException, SearchServiceConnectionException, StorageUnavailableException, StorageNotFoundException { log.debug("endpoint modify database image, databaseId={}, data.key={}", databaseId, data.getKey()); @@ -439,8 +437,8 @@ public class DatabaseEndpoint extends AbstractEndpoint { image = storageService.getBytes(data.getKey()); } return ResponseEntity.accepted() - .body(databaseMapper.customDatabaseToDatabaseDto( - databaseService.modifyImage(database, image))); + .body(databaseMapper.databaseDtoToDatabaseBriefDto(databaseMapper.databaseToDatabaseDto( + databaseService.modifyImage(database, image)))); } @GetMapping("/{databaseId}/image") @@ -480,7 +478,7 @@ public class DatabaseEndpoint extends AbstractEndpoint { @Header(name = "Access-Control-Expose-Headers", description = "Expose custom headers", schema = @Schema(implementation = String.class))}, content = {@Content( mediaType = "application/json", - schema = @Schema(implementation = DatabaseDto.class))}), + schema = @Schema(implementation = DatabaseBriefDto.class))}), @ApiResponse(responseCode = "403", description = "Not allowed to view database", content = {@Content( @@ -546,7 +544,7 @@ public class DatabaseEndpoint extends AbstractEndpoint { .toList()); database.setAccesses(List.of()); } - final DatabaseDto dto = databaseMapper.customDatabaseToDatabaseDto(database); + final DatabaseDto dto = databaseMapper.databaseToDatabaseDto(database); final HttpHeaders headers = new HttpHeaders(); if (isSystem(principal)) { headers.set("X-Username", database.getContainer().getPrivilegedUsername()); diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java index 9f4542fc02..39d7f61460 100644 --- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java +++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/TableEndpoint.java @@ -370,7 +370,7 @@ public class TableEndpoint extends AbstractEndpoint { endpointValidator.validateOnlyAccess(database, principal, true); endpointValidator.validateColumnCreateConstraints(data); return ResponseEntity.status(HttpStatus.CREATED) - .body(metadataMapper.customTableToTableDto( + .body(metadataMapper.tableToTableDto( tableService.createTable(database, data, principal))); } @@ -428,7 +428,7 @@ public class TableEndpoint extends AbstractEndpoint { throw new NotAllowedException("Failed to update table: not owner"); } return ResponseEntity.accepted() - .body(metadataMapper.customTableToTableDto( + .body(metadataMapper.tableToTableDto( tableService.updateTable(table, data))); } @@ -513,7 +513,7 @@ public class TableEndpoint extends AbstractEndpoint { } return ResponseEntity.ok() .headers(headers) - .body(metadataMapper.customTableToTableDto(table)); + .body(metadataMapper.tableToTableDto(table)); } @DeleteMapping("/{tableId}") diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/DatabaseEndpointUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/DatabaseEndpointUnitTest.java index 11d64faf8b..371e710fae 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/DatabaseEndpointUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/endpoints/DatabaseEndpointUnitTest.java @@ -198,7 +198,7 @@ public class DatabaseEndpointUnitTest extends AbstractUnitTest { .thenReturn(DATABASE_1); /* test */ - final ResponseEntity<DatabaseDto> response = databaseEndpoint.refreshTableMetadata(DATABASE_1_ID, USER_1_PRINCIPAL); + final ResponseEntity<DatabaseBriefDto> response = databaseEndpoint.refreshTableMetadata(DATABASE_1_ID, USER_1_PRINCIPAL); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getBody()); } @@ -218,7 +218,7 @@ public class DatabaseEndpointUnitTest extends AbstractUnitTest { .thenReturn(DATABASE_1); /* test */ - final ResponseEntity<DatabaseDto> response = databaseEndpoint.refreshViewMetadata(DATABASE_1_ID, USER_1_PRINCIPAL); + final ResponseEntity<DatabaseBriefDto> response = databaseEndpoint.refreshViewMetadata(DATABASE_1_ID, USER_1_PRINCIPAL); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getBody()); } @@ -682,7 +682,7 @@ public class DatabaseEndpointUnitTest extends AbstractUnitTest { .thenReturn(DATABASE_1); /* test */ - final ResponseEntity<DatabaseDto> response = databaseEndpoint.create(data, principal); + final ResponseEntity<DatabaseBriefDto> response = databaseEndpoint.create(data, principal); assertEquals(HttpStatus.CREATED, response.getStatusCode()); assertNotNull(response.getBody()); } @@ -704,7 +704,7 @@ public class DatabaseEndpointUnitTest extends AbstractUnitTest { } /* test */ - final ResponseEntity<DatabaseDto> response = databaseEndpoint.visibility(databaseId, data, principal); + final ResponseEntity<DatabaseBriefDto> response = databaseEndpoint.visibility(databaseId, data, principal); assertEquals(HttpStatus.ACCEPTED, response.getStatusCode()); assertNotNull(response.getBody()); } diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/gateway/DataServiceGatewayUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/gateway/DataServiceGatewayUnitTest.java index daeb1c1a96..c423d6b1ae 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/gateway/DataServiceGatewayUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/gateway/DataServiceGatewayUnitTest.java @@ -2,7 +2,7 @@ package at.tuwien.gateway; import at.tuwien.ExportResourceDto; import at.tuwien.api.database.AccessTypeDto; -import at.tuwien.api.database.DatabaseDto; +import at.tuwien.api.database.DatabaseBriefDto; import at.tuwien.api.database.ViewDto; import at.tuwien.api.database.query.QueryDto; import at.tuwien.api.database.table.TableDto; @@ -252,9 +252,9 @@ public class DataServiceGatewayUnitTest extends AbstractUnitTest { DatabaseNotFoundException { /* mock */ - when(dataServiceRestTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(DatabaseDto.class))) + when(dataServiceRestTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(DatabaseBriefDto.class))) .thenReturn(ResponseEntity.status(HttpStatus.CREATED) - .body(DATABASE_1_DTO)); + .body(DATABASE_1_BRIEF_DTO)); /* test */ dataServiceGateway.createDatabase(DATABASE_1_CREATE_INTERNAL); @@ -266,7 +266,7 @@ public class DataServiceGatewayUnitTest extends AbstractUnitTest { /* mock */ doThrow(HttpServerErrorException.class) .when(dataServiceRestTemplate) - .exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(DatabaseDto.class)); + .exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(DatabaseBriefDto.class)); /* test */ assertThrows(DataServiceConnectionException.class, () -> { @@ -280,7 +280,7 @@ public class DataServiceGatewayUnitTest extends AbstractUnitTest { /* mock */ doThrow(HttpClientErrorException.Unauthorized.class) .when(dataServiceRestTemplate) - .exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(DatabaseDto.class)); + .exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(DatabaseBriefDto.class)); /* test */ assertThrows(DataServiceException.class, () -> { @@ -294,7 +294,7 @@ public class DataServiceGatewayUnitTest extends AbstractUnitTest { /* mock */ doThrow(HttpClientErrorException.BadRequest.class) .when(dataServiceRestTemplate) - .exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(DatabaseDto.class)); + .exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(DatabaseBriefDto.class)); /* test */ assertThrows(DataServiceException.class, () -> { @@ -306,7 +306,7 @@ public class DataServiceGatewayUnitTest extends AbstractUnitTest { public void createDatabase_responseCode_fails() { /* mock */ - when(dataServiceRestTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(DatabaseDto.class))) + when(dataServiceRestTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(DatabaseBriefDto.class))) .thenReturn(ResponseEntity.status(HttpStatus.NO_CONTENT) .build()); diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/gateway/SearchServiceGatewayUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/gateway/SearchServiceGatewayUnitTest.java index b1ce21d4e5..b39dd06bac 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/gateway/SearchServiceGatewayUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/gateway/SearchServiceGatewayUnitTest.java @@ -1,7 +1,7 @@ package at.tuwien.gateway; import at.tuwien.test.AbstractUnitTest; -import at.tuwien.api.database.DatabaseDto; +import at.tuwien.api.database.DatabaseBriefDto; import at.tuwien.exception.*; import lombok.extern.log4j.Log4j2; import org.junit.jupiter.api.Test; @@ -37,11 +37,11 @@ public class SearchServiceGatewayUnitTest extends AbstractUnitTest { @Test public void update_succeeds() throws DatabaseNotFoundException, SearchServiceException, SearchServiceConnectionException { - final ResponseEntity<DatabaseDto> mock = ResponseEntity.accepted() + final ResponseEntity<DatabaseBriefDto> mock = ResponseEntity.accepted() .build(); /* mock */ - when(restTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseDto.class))) + when(restTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseBriefDto.class))) .thenReturn(mock); /* test */ @@ -50,11 +50,11 @@ public class SearchServiceGatewayUnitTest extends AbstractUnitTest { @Test public void update_badRequest_fails() { - final ResponseEntity<DatabaseDto> mock = ResponseEntity.status(HttpStatus.BAD_REQUEST) + final ResponseEntity<DatabaseBriefDto> mock = ResponseEntity.status(HttpStatus.BAD_REQUEST) .build(); /* mock */ - when(restTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseDto.class))) + when(restTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseBriefDto.class))) .thenReturn(mock); /* test */ @@ -65,11 +65,11 @@ public class SearchServiceGatewayUnitTest extends AbstractUnitTest { @Test public void update_unexpectedResponse_fails() { - final ResponseEntity<DatabaseDto> mock = ResponseEntity.status(HttpStatus.OK) + final ResponseEntity<DatabaseBriefDto> mock = ResponseEntity.status(HttpStatus.OK) .build(); /* mock */ - when(restTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseDto.class))) + when(restTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseBriefDto.class))) .thenReturn(mock); /* test */ @@ -84,7 +84,7 @@ public class SearchServiceGatewayUnitTest extends AbstractUnitTest { /* mock */ doThrow(HttpServerErrorException.ServiceUnavailable.class) .when(restTemplate) - .exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseDto.class)); + .exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseBriefDto.class)); /* test */ assertThrows(SearchServiceConnectionException.class, () -> { @@ -98,7 +98,7 @@ public class SearchServiceGatewayUnitTest extends AbstractUnitTest { /* mock */ doThrow(HttpClientErrorException.NotFound.class) .when(restTemplate) - .exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseDto.class)); + .exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseBriefDto.class)); /* test */ assertThrows(DatabaseNotFoundException.class, () -> { diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/mapper/MetadataMapperUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/mapper/MetadataMapperUnitTest.java index 9b778e67fb..6505506eea 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/mapper/MetadataMapperUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/mapper/MetadataMapperUnitTest.java @@ -1,13 +1,7 @@ package at.tuwien.mapper; -import at.tuwien.api.database.DatabaseDto; -import at.tuwien.api.database.ViewBriefDto; -import at.tuwien.api.database.ViewDto; -import at.tuwien.api.database.table.TableBriefDto; -import at.tuwien.api.identifier.IdentifierDto; import at.tuwien.api.identifier.IdentifierTypeDto; import at.tuwien.api.user.UserBriefDto; -import at.tuwien.api.user.UserDto; import at.tuwien.entities.identifier.Identifier; import at.tuwien.entities.identifier.IdentifierType; import at.tuwien.test.AbstractUnitTest; diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/AccessServiceUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/AccessServiceUnitTest.java index c647cdbd74..1c96e6283d 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/AccessServiceUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/AccessServiceUnitTest.java @@ -3,7 +3,7 @@ package at.tuwien.service; import at.tuwien.exception.*; import at.tuwien.test.AbstractUnitTest; import at.tuwien.api.database.AccessTypeDto; -import at.tuwien.api.database.DatabaseDto; +import at.tuwien.api.database.DatabaseBriefDto; import at.tuwien.entities.database.AccessType; import at.tuwien.entities.database.Database; import at.tuwien.entities.database.DatabaseAccess; @@ -80,7 +80,7 @@ public class AccessServiceUnitTest extends AbstractUnitTest { when(dataServiceRestTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(Void.class))) .thenReturn(ResponseEntity.status(HttpStatus.CREATED) .build()); - when(searchServiceRestTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseDto.class))) + when(searchServiceRestTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseBriefDto.class))) .thenReturn(ResponseEntity.accepted() .build()); @@ -155,7 +155,7 @@ public class AccessServiceUnitTest extends AbstractUnitTest { .build()); doThrow(HttpClientErrorException.BadRequest.class) .when(searchServiceRestTemplate) - .exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseDto.class)); + .exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseBriefDto.class)); /* test */ assertThrows(SearchServiceException.class, () -> { @@ -174,7 +174,7 @@ public class AccessServiceUnitTest extends AbstractUnitTest { .build()); doThrow(HttpClientErrorException.Unauthorized.class) .when(searchServiceRestTemplate) - .exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseDto.class)); + .exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseBriefDto.class)); /* test */ assertThrows(SearchServiceException.class, () -> { @@ -193,7 +193,7 @@ public class AccessServiceUnitTest extends AbstractUnitTest { .build()); doThrow(HttpClientErrorException.NotFound.class) .when(searchServiceRestTemplate) - .exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseDto.class)); + .exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseBriefDto.class)); /* test */ assertThrows(DatabaseNotFoundException.class, () -> { @@ -212,7 +212,7 @@ public class AccessServiceUnitTest extends AbstractUnitTest { .build()); doThrow(HttpServerErrorException.InternalServerError.class) .when(searchServiceRestTemplate) - .exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseDto.class)); + .exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseBriefDto.class)); /* test */ assertThrows(SearchServiceConnectionException.class, () -> { @@ -230,7 +230,7 @@ public class AccessServiceUnitTest extends AbstractUnitTest { when(dataServiceRestTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(Void.class))) .thenReturn(ResponseEntity.accepted() .build()); - when(searchServiceRestTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseDto.class))) + when(searchServiceRestTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseBriefDto.class))) .thenReturn(ResponseEntity.accepted() .build()); @@ -305,7 +305,7 @@ public class AccessServiceUnitTest extends AbstractUnitTest { .build()); doThrow(HttpClientErrorException.BadRequest.class) .when(searchServiceRestTemplate) - .exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseDto.class)); + .exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseBriefDto.class)); /* test */ assertThrows(SearchServiceException.class, () -> { @@ -324,7 +324,7 @@ public class AccessServiceUnitTest extends AbstractUnitTest { .build()); doThrow(HttpClientErrorException.Unauthorized.class) .when(searchServiceRestTemplate) - .exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseDto.class)); + .exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseBriefDto.class)); /* test */ assertThrows(SearchServiceException.class, () -> { @@ -343,7 +343,7 @@ public class AccessServiceUnitTest extends AbstractUnitTest { .build()); doThrow(HttpClientErrorException.NotFound.class) .when(searchServiceRestTemplate) - .exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseDto.class)); + .exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseBriefDto.class)); /* test */ assertThrows(DatabaseNotFoundException.class, () -> { @@ -362,7 +362,7 @@ public class AccessServiceUnitTest extends AbstractUnitTest { .build()); doThrow(HttpServerErrorException.InternalServerError.class) .when(searchServiceRestTemplate) - .exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseDto.class)); + .exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseBriefDto.class)); /* test */ assertThrows(SearchServiceConnectionException.class, () -> { @@ -382,7 +382,7 @@ public class AccessServiceUnitTest extends AbstractUnitTest { when(dataServiceRestTemplate.exchange(anyString(), eq(HttpMethod.DELETE), any(HttpEntity.class), eq(Void.class))) .thenReturn(ResponseEntity.accepted() .build()); - when(searchServiceRestTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseDto.class))) + when(searchServiceRestTemplate.exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseBriefDto.class))) .thenReturn(ResponseEntity.accepted() .build()); @@ -445,7 +445,7 @@ public class AccessServiceUnitTest extends AbstractUnitTest { .build()); doThrow(HttpClientErrorException.BadRequest.class) .when(searchServiceRestTemplate) - .exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseDto.class)); + .exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseBriefDto.class)); /* test */ assertThrows(SearchServiceException.class, () -> { @@ -466,7 +466,7 @@ public class AccessServiceUnitTest extends AbstractUnitTest { .build()); doThrow(HttpClientErrorException.Unauthorized.class) .when(searchServiceRestTemplate) - .exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseDto.class)); + .exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseBriefDto.class)); /* test */ assertThrows(SearchServiceException.class, () -> { @@ -487,7 +487,7 @@ public class AccessServiceUnitTest extends AbstractUnitTest { .build()); doThrow(HttpClientErrorException.NotFound.class) .when(searchServiceRestTemplate) - .exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseDto.class)); + .exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseBriefDto.class)); /* test */ assertThrows(DatabaseNotFoundException.class, () -> { @@ -508,7 +508,7 @@ public class AccessServiceUnitTest extends AbstractUnitTest { .build()); doThrow(HttpServerErrorException.InternalServerError.class) .when(searchServiceRestTemplate) - .exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseDto.class)); + .exchange(anyString(), eq(HttpMethod.PUT), any(HttpEntity.class), eq(DatabaseBriefDto.class)); /* test */ assertThrows(SearchServiceConnectionException.class, () -> { diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/DataCiteIdentifierServicePersistenceTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/DataCiteIdentifierServicePersistenceTest.java index 182fe8e14a..b77bc30d38 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/DataCiteIdentifierServicePersistenceTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/DataCiteIdentifierServicePersistenceTest.java @@ -1,20 +1,20 @@ package at.tuwien.service; +import at.tuwien.api.datacite.DataCiteBody; +import at.tuwien.api.datacite.doi.DataCiteDoi; import at.tuwien.api.identifier.BibliographyTypeDto; +import at.tuwien.entities.database.Database; import at.tuwien.entities.identifier.Creator; import at.tuwien.entities.identifier.Identifier; import at.tuwien.entities.identifier.IdentifierStatusType; import at.tuwien.entities.identifier.NameIdentifierSchemeType; +import at.tuwien.exception.*; +import at.tuwien.gateway.SearchServiceGateway; import at.tuwien.repository.ContainerRepository; import at.tuwien.repository.DatabaseRepository; import at.tuwien.repository.LicenseRepository; import at.tuwien.repository.UserRepository; import at.tuwien.test.AbstractUnitTest; -import at.tuwien.api.datacite.DataCiteBody; -import at.tuwien.api.datacite.doi.DataCiteDoi; -import at.tuwien.entities.database.Database; -import at.tuwien.exception.*; -import at.tuwien.gateway.SearchServiceGateway; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -141,7 +141,7 @@ public class DataCiteIdentifierServicePersistenceTest extends AbstractUnitTest { when(restTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(dataCiteBodyParameterizedTypeReference))) .thenReturn(mock); when(searchServiceGateway.update(any(Database.class))) - .thenReturn(DATABASE_1_DTO); + .thenReturn(DATABASE_1_BRIEF_DTO); /* test */ dataCiteIdentifierService.save(DATABASE_1, USER_1, IDENTIFIER_1_SAVE_DTO); @@ -156,7 +156,7 @@ public class DataCiteIdentifierServicePersistenceTest extends AbstractUnitTest { .when(restTemplate) .exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(dataCiteBodyParameterizedTypeReference)); when(searchServiceGateway.update(any(Database.class))) - .thenReturn(DATABASE_1_DTO); + .thenReturn(DATABASE_1_BRIEF_DTO); /* test */ assertThrows(MalformedException.class, () -> { @@ -173,7 +173,7 @@ public class DataCiteIdentifierServicePersistenceTest extends AbstractUnitTest { .when(restTemplate) .exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class), eq(dataCiteBodyParameterizedTypeReference)); when(searchServiceGateway.update(any(Database.class))) - .thenReturn(DATABASE_1_DTO); + .thenReturn(DATABASE_1_BRIEF_DTO); /* test */ assertThrows(DataServiceConnectionException.class, () -> { @@ -332,7 +332,7 @@ public class DataCiteIdentifierServicePersistenceTest extends AbstractUnitTest { /* mock */ when(searchServiceGateway.update(any(Database.class))) - .thenReturn(DATABASE_1_DTO); + .thenReturn(DATABASE_1_BRIEF_DTO); /* test */ dataCiteIdentifierService.delete(IDENTIFIER_1); diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/DatabaseServiceUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/DatabaseServiceUnitTest.java index 1b6570abd8..18d037fe45 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/DatabaseServiceUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/DatabaseServiceUnitTest.java @@ -110,7 +110,7 @@ public class DatabaseServiceUnitTest extends AbstractUnitTest { when(databaseRepository.save(any(Database.class))) .thenReturn(DATABASE_1); when(searchServiceGateway.update(any(Database.class))) - .thenReturn(DATABASE_1_DTO); + .thenReturn(DATABASE_1_BRIEF_DTO); /* test */ final Database response = databaseService.modifyImage(DATABASE_1, image); @@ -164,7 +164,7 @@ public class DatabaseServiceUnitTest extends AbstractUnitTest { when(databaseRepository.save(any(Database.class))) .thenReturn(DATABASE_1); when(searchServiceGateway.update(any(Database.class))) - .thenReturn(DATABASE_1_DTO); + .thenReturn(DATABASE_1_BRIEF_DTO); /* test */ final Database response = databaseService.updateViewMetadata(DATABASE_1); @@ -222,7 +222,7 @@ public class DatabaseServiceUnitTest extends AbstractUnitTest { when(databaseRepository.save(any(Database.class))) .thenReturn(DATABASE_1); when(searchServiceGateway.update(any(Database.class))) - .thenReturn(DATABASE_1_DTO); + .thenReturn(DATABASE_1_BRIEF_DTO); /* test */ final Database response = databaseService.updateViewMetadata(DATABASE_1); @@ -240,7 +240,7 @@ public class DatabaseServiceUnitTest extends AbstractUnitTest { when(databaseRepository.save(any(Database.class))) .thenReturn(DATABASE_1); when(searchServiceGateway.update(any(Database.class))) - .thenReturn(DATABASE_1_DTO); + .thenReturn(DATABASE_1_BRIEF_DTO); /* test */ final Database response = databaseService.updateViewMetadata(DATABASE_1); @@ -258,7 +258,7 @@ public class DatabaseServiceUnitTest extends AbstractUnitTest { when(databaseRepository.save(any(Database.class))) .thenReturn(DATABASE_1); when(searchServiceGateway.update(any(Database.class))) - .thenReturn(DATABASE_1_DTO); + .thenReturn(DATABASE_1_BRIEF_DTO); /* test */ final Database response = databaseService.updateTableMetadata(DATABASE_1); @@ -276,7 +276,7 @@ public class DatabaseServiceUnitTest extends AbstractUnitTest { when(databaseRepository.save(any(Database.class))) .thenReturn(DATABASE_1); when(searchServiceGateway.update(any(Database.class))) - .thenReturn(DATABASE_1_DTO); + .thenReturn(DATABASE_1_BRIEF_DTO); /* test */ final Database response = databaseService.updateTableMetadata(DATABASE_1); @@ -294,7 +294,7 @@ public class DatabaseServiceUnitTest extends AbstractUnitTest { when(databaseRepository.save(any(Database.class))) .thenReturn(DATABASE_1); when(searchServiceGateway.update(any(Database.class))) - .thenReturn(DATABASE_1_DTO); + .thenReturn(DATABASE_1_BRIEF_DTO); /* test */ final Database response = databaseService.updateTableMetadata(DATABASE_1); @@ -517,7 +517,7 @@ public class DatabaseServiceUnitTest extends AbstractUnitTest { /* mock */ when(searchServiceGateway.update(any(Database.class))) - .thenReturn(DATABASE_1_DTO); + .thenReturn(DATABASE_1_BRIEF_DTO); when(databaseRepository.save(any(Database.class))) .thenReturn(DATABASE_1); diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/IdentifierServicePersistenceTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/IdentifierServicePersistenceTest.java index 0c87dcdd69..40fc28fe4d 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/IdentifierServicePersistenceTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/IdentifierServicePersistenceTest.java @@ -176,7 +176,7 @@ public class IdentifierServicePersistenceTest extends AbstractUnitTest { when(dataServiceGateway.findQuery(IDENTIFIER_5_DATABASE_ID, IDENTIFIER_5_QUERY_ID)) .thenReturn(QUERY_2_DTO); when(searchServiceGateway.update(any(Database.class))) - .thenReturn(DATABASE_2_DTO); + .thenReturn(DATABASE_2_BRIEF_DTO); /* test */ identifierService.save(DATABASE_2, USER_2, IDENTIFIER_5_SAVE_DTO); @@ -286,7 +286,7 @@ public class IdentifierServicePersistenceTest extends AbstractUnitTest { /* mock */ when(searchServiceGateway.update(any(Database.class))) - .thenReturn(DATABASE_1_DTO); + .thenReturn(DATABASE_1_BRIEF_DTO); /* test */ identifierService.delete(IDENTIFIER_1); diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/TableServicePersistenceTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/TableServicePersistenceTest.java index 3126f9e9f4..5250f7e106 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/TableServicePersistenceTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/TableServicePersistenceTest.java @@ -112,7 +112,7 @@ public class TableServicePersistenceTest extends AbstractUnitTest { .when(dataServiceGateway) .createTable(DATABASE_1_ID, request); when(searchServiceGateway.update(any(Database.class))) - .thenReturn(DATABASE_1_DTO); + .thenReturn(DATABASE_1_BRIEF_DTO); /* test */ final Table response = tableService.createTable(DATABASE_1, request, USER_1_PRINCIPAL); diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/TableServiceUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/TableServiceUnitTest.java index d975e808e3..5fb8e9ad7e 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/TableServiceUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/TableServiceUnitTest.java @@ -135,7 +135,7 @@ public class TableServiceUnitTest extends AbstractUnitTest { when(databaseRepository.save(any(Database.class))) .thenReturn(DATABASE_1); when(searchServiceGateway.update(any(Database.class))) - .thenReturn(DATABASE_1_DTO); + .thenReturn(DATABASE_1_BRIEF_DTO); /* test */ tableService.updateStatistics(TABLE_8); @@ -224,7 +224,7 @@ public class TableServiceUnitTest extends AbstractUnitTest { when(databaseRepository.save(any(Database.class))) .thenReturn(DATABASE_1); when(searchServiceGateway.update(any(Database.class))) - .thenReturn(DATABASE_1_DTO); + .thenReturn(DATABASE_1_BRIEF_DTO); /* test */ final TableColumn response = tableService.update(TABLE_1_COLUMNS.get(0), request); @@ -256,7 +256,7 @@ public class TableServiceUnitTest extends AbstractUnitTest { when(databaseRepository.save(any(Database.class))) .thenReturn(DATABASE_1); when(searchServiceGateway.update(any(Database.class))) - .thenReturn(DATABASE_1_DTO); + .thenReturn(DATABASE_1_BRIEF_DTO); /* test */ final TableColumn response = tableService.update(TABLE_1_COLUMNS.get(0), request); @@ -279,7 +279,7 @@ public class TableServiceUnitTest extends AbstractUnitTest { when(databaseRepository.save(any(Database.class))) .thenReturn(DATABASE_1); when(searchServiceGateway.update(any(Database.class))) - .thenReturn(DATABASE_1_DTO); + .thenReturn(DATABASE_1_BRIEF_DTO); /* test */ final Table response = tableService.createTable(DATABASE_1, TABLE_3_CREATE_DTO, USER_1_PRINCIPAL); @@ -316,7 +316,7 @@ public class TableServiceUnitTest extends AbstractUnitTest { when(databaseRepository.save(any(Database.class))) .thenReturn(DATABASE_1); when(searchServiceGateway.update(any(Database.class))) - .thenReturn(DATABASE_1_DTO); + .thenReturn(DATABASE_1_BRIEF_DTO); /* test */ final Table response = tableService.createTable(DATABASE_1, request, USER_1_PRINCIPAL); @@ -369,7 +369,7 @@ public class TableServiceUnitTest extends AbstractUnitTest { when(databaseRepository.save(any(Database.class))) .thenReturn(DATABASE_1); when(searchServiceGateway.update(any(Database.class))) - .thenReturn(DATABASE_1_DTO); + .thenReturn(DATABASE_1_BRIEF_DTO); /* test */ assertThrows(MalformedException.class, () -> { @@ -392,7 +392,7 @@ public class TableServiceUnitTest extends AbstractUnitTest { .when(dataServiceGateway) .createTable(DATABASE_1_ID, TABLE_3_CREATE_DTO); when(searchServiceGateway.update(any(Database.class))) - .thenReturn(DATABASE_1_DTO); + .thenReturn(DATABASE_1_BRIEF_DTO); /* test */ final Table response = tableService.createTable(DATABASE_1, TABLE_3_CREATE_DTO, USER_1_PRINCIPAL); @@ -413,7 +413,7 @@ public class TableServiceUnitTest extends AbstractUnitTest { .when(dataServiceGateway) .createTable(DATABASE_1_ID, TABLE_5_CREATE_DTO); when(searchServiceGateway.update(any(Database.class))) - .thenReturn(DATABASE_1_DTO); + .thenReturn(DATABASE_1_BRIEF_DTO); /* test */ assertThrows(DataServiceException.class, () -> { @@ -511,7 +511,7 @@ public class TableServiceUnitTest extends AbstractUnitTest { .when(dataServiceGateway) .deleteTable(DATABASE_1_ID, TABLE_1_ID); when(searchServiceGateway.update(any(Database.class))) - .thenReturn(DATABASE_1_DTO); + .thenReturn(DATABASE_1_BRIEF_DTO); /* test */ tableService.deleteTable(TABLE_1); @@ -527,7 +527,7 @@ public class TableServiceUnitTest extends AbstractUnitTest { .when(dataServiceGateway) .deleteTable(DATABASE_1_ID, TABLE_4_ID); when(searchServiceGateway.update(any(Database.class))) - .thenReturn(DATABASE_1_DTO); + .thenReturn(DATABASE_1_BRIEF_DTO); /* test */ tableService.deleteTable(TABLE_4); diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/ViewServicePersistenceTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/ViewServicePersistenceTest.java index 8ca002472a..57a84965dc 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/ViewServicePersistenceTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/ViewServicePersistenceTest.java @@ -89,7 +89,7 @@ public class ViewServicePersistenceTest extends AbstractUnitTest { .when(dataServiceGateway) .deleteView(DATABASE_1_ID, VIEW_1_ID); when(searchServiceGateway.update(any(Database.class))) - .thenReturn(DATABASE_1_DTO); + .thenReturn(DATABASE_1_BRIEF_DTO); /* test */ viewService.delete(VIEW_1); diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/ViewServiceUnitTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/ViewServiceUnitTest.java index cd9fe03c65..c63d207e6e 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/ViewServiceUnitTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/ViewServiceUnitTest.java @@ -62,7 +62,7 @@ public class ViewServiceUnitTest extends AbstractUnitTest { when(databaseRepository.save(any(Database.class))) .thenReturn(DATABASE_1); when(searchServiceGateway.update(any(Database.class))) - .thenReturn(DATABASE_1_DTO); + .thenReturn(DATABASE_1_BRIEF_DTO); /* test */ final View response = viewService.create(DATABASE_1, USER_1, request); @@ -117,7 +117,7 @@ public class ViewServiceUnitTest extends AbstractUnitTest { when(databaseRepository.save(any(Database.class))) .thenReturn(DATABASE_1); when(searchServiceGateway.update(any(Database.class))) - .thenReturn(DATABASE_1_DTO); + .thenReturn(DATABASE_1_BRIEF_DTO); /* test */ viewService.delete(VIEW_1); diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/gateway/SearchServiceGateway.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/gateway/SearchServiceGateway.java index f5e2f49c02..6632a08194 100644 --- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/gateway/SearchServiceGateway.java +++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/gateway/SearchServiceGateway.java @@ -1,12 +1,12 @@ package at.tuwien.gateway; -import at.tuwien.api.database.DatabaseDto; +import at.tuwien.api.database.DatabaseBriefDto; import at.tuwien.entities.database.Database; import at.tuwien.exception.*; public interface SearchServiceGateway { - DatabaseDto update(Database database) throws SearchServiceConnectionException, SearchServiceException, DatabaseNotFoundException; + DatabaseBriefDto update(Database database) throws SearchServiceConnectionException, SearchServiceException, DatabaseNotFoundException; void delete(Long databaseId) throws SearchServiceConnectionException, SearchServiceException, DatabaseNotFoundException; } diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/gateway/impl/SearchServiceGatewayImpl.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/gateway/impl/SearchServiceGatewayImpl.java index 0f14b8d348..503cad47ec 100644 --- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/gateway/impl/SearchServiceGatewayImpl.java +++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/gateway/impl/SearchServiceGatewayImpl.java @@ -1,6 +1,6 @@ package at.tuwien.gateway.impl; -import at.tuwien.api.database.DatabaseDto; +import at.tuwien.api.database.DatabaseBriefDto; import at.tuwien.config.GatewayConfig; import at.tuwien.entities.database.Database; import at.tuwien.exception.DatabaseNotFoundException; @@ -35,8 +35,8 @@ public class SearchServiceGatewayImpl implements SearchServiceGateway { } @Override - public DatabaseDto update(Database database) throws SearchServiceConnectionException, SearchServiceException, DatabaseNotFoundException { - final ResponseEntity<DatabaseDto> response; + public DatabaseBriefDto update(Database database) throws SearchServiceConnectionException, SearchServiceException, DatabaseNotFoundException { + final ResponseEntity<DatabaseBriefDto> response; final HttpHeaders headers = new HttpHeaders(); headers.set("Accept", "application/json"); headers.set("Content-Type", "application/json"); @@ -44,7 +44,7 @@ public class SearchServiceGatewayImpl implements SearchServiceGateway { log.trace("update database at endpoint {} with path {}", gatewayConfig.getSearchEndpoint(), path); try { response = restTemplate.exchange(path, HttpMethod.PUT, new HttpEntity<>( - metadataMapper.databaseToPrivilegedDatabaseDto(database), headers), DatabaseDto.class); + metadataMapper.databaseToDatabaseDto(database), headers), DatabaseBriefDto.class); } catch (ResourceAccessException | HttpServerErrorException.ServiceUnavailable | HttpServerErrorException.InternalServerError e) { log.error("Failed to update database: {}", e.getMessage()); diff --git a/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/AbstractUnitTest.java b/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/AbstractUnitTest.java index 91936adaf8..232b4cd280 100644 --- a/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/AbstractUnitTest.java +++ b/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/AbstractUnitTest.java @@ -12,7 +12,7 @@ public abstract class AbstractUnitTest extends BaseTest { public void genesis() { IMAGE_1_DTO.setOperators(IMAGE_1_OPERATORS_DTO); - CONTAINER_1_PRIVILEGED_DTO.setImage(IMAGE_1_DTO); + CONTAINER_1_DTO.setImage(IMAGE_1_DTO); IMAGE_1.setOperators(new LinkedList<>(IMAGE_1_OPERATORS)); CONTAINER_1.setDatabases(new LinkedList<>(List.of(DATABASE_1, DATABASE_2, DATABASE_3))); CONTAINER_4.setDatabases(new LinkedList<>(List.of(DATABASE_4))); @@ -36,13 +36,12 @@ public abstract class AbstractUnitTest extends BaseTest { DATABASE_1.setIsSchemaPublic(DATABASE_1_SCHEMA_PUBLIC); DATABASE_1_USER_1_READ_ACCESS.setType(AccessType.READ); DATABASE_1.setAccesses(new LinkedList<>(List.of(DATABASE_1_USER_1_READ_ACCESS, DATABASE_1_USER_2_WRITE_OWN_ACCESS, DATABASE_1_USER_3_WRITE_ALL_ACCESS))); - DATABASE_1_PRIVILEGED_DTO.setAccesses(new LinkedList<>(List.of(DATABASE_1_USER_1_READ_ACCESS_DTO, DATABASE_1_USER_2_WRITE_OWN_ACCESS_DTO, DATABASE_1_USER_3_WRITE_ALL_ACCESS_DTO))); + DATABASE_1_DTO.setAccesses(new LinkedList<>(List.of(DATABASE_1_USER_1_READ_ACCESS_DTO, DATABASE_1_USER_2_WRITE_OWN_ACCESS_DTO, DATABASE_1_USER_3_WRITE_ALL_ACCESS_DTO))); TABLE_1.setDatabase(DATABASE_1); UNIT_1.setId(UNIT_1_ID); TABLE_1.setColumns(new LinkedList<>(TABLE_1_COLUMNS)); TABLE_1.setConstraints(TABLE_1_CONSTRAINTS); - TABLE_1_PRIVILEGED_DTO.setColumns(new LinkedList<>(TABLE_1_COLUMNS_DTO)); - TABLE_1_PRIVILEGED_DTO.setDatabase(DATABASE_1_PRIVILEGED_DTO); + TABLE_1_DTO.setColumns(new LinkedList<>(TABLE_1_COLUMNS_DTO)); VIEW_1_DTO.setIdentifiers(VIEW_1_DTO_IDENTIFIERS); DATABASE_1.setIdentifiers(new LinkedList<>(List.of(IDENTIFIER_1, IDENTIFIER_2, IDENTIFIER_3, IDENTIFIER_4))); IDENTIFIER_1.setDatabase(DATABASE_1); @@ -51,18 +50,17 @@ public abstract class AbstractUnitTest extends BaseTest { IDENTIFIER_4.setDatabase(DATABASE_1); DATABASE_1.setTables(new LinkedList<>(List.of(TABLE_1, TABLE_2, TABLE_3, TABLE_4))); DATABASE_1.setViews(new LinkedList<>(List.of(VIEW_1, VIEW_2, VIEW_3))); - DATABASE_1_PRIVILEGED_DTO.setContainer(CONTAINER_1_PRIVILEGED_DTO); - DATABASE_1_PRIVILEGED_DTO.setIdentifiers(new LinkedList<>(List.of(IDENTIFIER_1_DTO, IDENTIFIER_2_DTO, IDENTIFIER_3_DTO, IDENTIFIER_4_DTO))); - DATABASE_1_PRIVILEGED_DTO.setTables(new LinkedList<>(List.of(TABLE_1_DTO, TABLE_2_DTO, TABLE_3_DTO, TABLE_4_DTO))); - DATABASE_1_PRIVILEGED_DTO.setViews(new LinkedList<>(List.of(VIEW_1_DTO, VIEW_2_DTO, VIEW_3_DTO))); + DATABASE_1_DTO.setContainer(CONTAINER_1_DTO); + DATABASE_1_DTO.setIdentifiers(new LinkedList<>(List.of(IDENTIFIER_1_DTO, IDENTIFIER_2_DTO, IDENTIFIER_3_DTO, IDENTIFIER_4_DTO))); + DATABASE_1_DTO.setTables(new LinkedList<>(List.of(TABLE_1_DTO, TABLE_2_DTO, TABLE_3_DTO, TABLE_4_DTO))); + DATABASE_1_DTO.setViews(new LinkedList<>(List.of(VIEW_1_DTO, VIEW_2_DTO, VIEW_3_DTO))); TABLE_1_DTO.setColumns(new LinkedList<>(TABLE_1_COLUMNS_DTO)); TABLE_1_DTO.setConstraints(TABLE_1_CONSTRAINTS_DTO); TABLE_2.setDatabase(DATABASE_1); TABLE_2.setColumns(new LinkedList<>(TABLE_2_COLUMNS)); TABLE_2_CONSTRAINTS.getForeignKeys().get(0).getReferences().get(0).setForeignKey(TABLE_2_CONSTRAINTS.getForeignKeys().get(0)); TABLE_2.setConstraints(TABLE_2_CONSTRAINTS); - TABLE_2_PRIVILEGED_DTO.setColumns(new LinkedList<>(TABLE_2_COLUMNS_DTO)); - TABLE_2_PRIVILEGED_DTO.setDatabase(DATABASE_1_PRIVILEGED_DTO); + TABLE_2_DTO.setColumns(new LinkedList<>(TABLE_2_COLUMNS_DTO)); TABLE_2_DTO.setColumns(new LinkedList<>(TABLE_2_COLUMNS_DTO)); TABLE_2_DTO.setConstraints(TABLE_2_CONSTRAINTS_DTO); TABLE_3.setDatabase(DATABASE_1); @@ -78,13 +76,10 @@ public abstract class AbstractUnitTest extends BaseTest { VIEW_1.setDatabase(DATABASE_1); VIEW_1.setColumns(new LinkedList<>(VIEW_1_COLUMNS)); VIEW_1.setIdentifiers(new LinkedList<>(List.of(IDENTIFIER_3))); - VIEW_1_PRIVILEGED_DTO.setDatabase(DATABASE_1_PRIVILEGED_DTO); VIEW_2.setDatabase(DATABASE_1); VIEW_2.setColumns(new LinkedList<>(VIEW_2_COLUMNS)); - VIEW_2_PRIVILEGED_DTO.setDatabase(DATABASE_1_PRIVILEGED_DTO); VIEW_3.setDatabase(DATABASE_1); VIEW_3.setColumns(new LinkedList<>(VIEW_3_COLUMNS)); - VIEW_3_PRIVILEGED_DTO.setDatabase(DATABASE_1_PRIVILEGED_DTO); IDENTIFIER_1.setDatabase(DATABASE_1); IDENTIFIER_2.setDatabase(DATABASE_1); IDENTIFIER_3.setDatabase(DATABASE_1); @@ -92,20 +87,19 @@ public abstract class AbstractUnitTest extends BaseTest { /* DATABASE 2 */ DATABASE_2.setSubsets(new LinkedList<>()); DATABASE_2.setAccesses(new LinkedList<>(List.of(DATABASE_2_USER_2_WRITE_ALL_ACCESS, DATABASE_2_USER_3_READ_ACCESS))); - DATABASE_2_PRIVILEGED_DTO.setAccesses(new LinkedList<>(List.of(DATABASE_2_USER_2_WRITE_ALL_ACCESS_DTO, DATABASE_2_USER_3_READ_ACCESS_DTO))); + DATABASE_2_DTO.setAccesses(new LinkedList<>(List.of(DATABASE_2_USER_2_WRITE_ALL_ACCESS_DTO, DATABASE_2_USER_3_READ_ACCESS_DTO))); DATABASE_2.setTables(new LinkedList<>(List.of(TABLE_5, TABLE_6, TABLE_7))); VIEW_4.setColumns(new LinkedList<>(VIEW_4_COLUMNS)); DATABASE_2.setViews(new LinkedList<>(List.of(VIEW_4))); DATABASE_2.setIdentifiers(new LinkedList<>(List.of(IDENTIFIER_5))); - DATABASE_2_PRIVILEGED_DTO.setTables(new LinkedList<>(List.of(TABLE_5_DTO, TABLE_6_DTO, TABLE_7_DTO))); - DATABASE_2_PRIVILEGED_DTO.setViews(new LinkedList<>(List.of(VIEW_4_DTO))); - DATABASE_2_PRIVILEGED_DTO.setIdentifiers(new LinkedList<>(List.of(IDENTIFIER_5_DTO))); + DATABASE_2_DTO.setTables(new LinkedList<>(List.of(TABLE_5_DTO, TABLE_6_DTO, TABLE_7_DTO))); + DATABASE_2_DTO.setViews(new LinkedList<>(List.of(VIEW_4_DTO))); + DATABASE_2_DTO.setIdentifiers(new LinkedList<>(List.of(IDENTIFIER_5_DTO))); TABLE_5.setDatabase(DATABASE_2); TABLE_5.setColumns(new LinkedList<>(TABLE_5_COLUMNS)); TABLE_5.setConstraints(TABLE_5_CONSTRAINTS); - TABLE_5_PRIVILEGED_DTO.setColumns(new LinkedList<>(TABLE_5_COLUMNS_DTO)); - TABLE_5_PRIVILEGED_DTO.setConstraints(TABLE_5_CONSTRAINTS_DTO); - TABLE_5_PRIVILEGED_DTO.setDatabase(DATABASE_2_PRIVILEGED_DTO); + TABLE_5_DTO.setColumns(new LinkedList<>(TABLE_5_COLUMNS_DTO)); + TABLE_5_DTO.setConstraints(TABLE_5_CONSTRAINTS_DTO); TABLE_5_DTO.setColumns(TABLE_5_COLUMNS_DTO); TABLE_5_DTO.setConstraints(TABLE_5_CONSTRAINTS_DTO); TABLE_6.setDatabase(DATABASE_2); @@ -128,27 +122,31 @@ public abstract class AbstractUnitTest extends BaseTest { DATABASE_3.setTables(new LinkedList<>(List.of(TABLE_8))); DATABASE_3.setViews(new LinkedList<>(List.of(VIEW_5))); DATABASE_3.setIdentifiers(new LinkedList<>(List.of(IDENTIFIER_6))); + DATABASE_3_DTO.setTables(new LinkedList<>(List.of(TABLE_8_DTO))); + DATABASE_3_DTO.setViews(new LinkedList<>(List.of(VIEW_5_DTO))); + DATABASE_3_DTO.setIdentifiers(new LinkedList<>(List.of(IDENTIFIER_6_DTO))); TABLE_8.setDatabase(DATABASE_3); TABLE_8.setColumns(new LinkedList<>(TABLE_8_COLUMNS)); TABLE_8.setConstraints(TABLE_8_CONSTRAINTS); TABLE_8_DTO.setColumns(new LinkedList<>(TABLE_8_COLUMNS_DTO)); TABLE_8_DTO.setConstraints(TABLE_8_CONSTRAINTS_DTO); - TABLE_8_PRIVILEGED_DTO.setColumns(new LinkedList<>(TABLE_8_COLUMNS_DTO)); - TABLE_8_PRIVILEGED_DTO.setConstraints(TABLE_8_CONSTRAINTS_DTO); - TABLE_8_PRIVILEGED_DTO.setDatabase(DATABASE_3_PRIVILEGED_DTO); + TABLE_8_DTO.setColumns(new LinkedList<>(TABLE_8_COLUMNS_DTO)); + TABLE_8_DTO.setConstraints(TABLE_8_CONSTRAINTS_DTO); VIEW_5.setDatabase(DATABASE_3); VIEW_5.setColumns(VIEW_5_COLUMNS); VIEW_5_DTO.setColumns(VIEW_5_COLUMNS_DTO); IDENTIFIER_6.setDatabase(DATABASE_3); /* DATABASE 4 */ + DATABASE_4.setSubsets(new LinkedList<>()); + DATABASE_4.setAccesses(new LinkedList<>(List.of(DATABASE_4_USER_1_READ_ACCESS, DATABASE_4_USER_2_WRITE_OWN_ACCESS, DATABASE_4_USER_3_WRITE_ALL_ACCESS))); + DATABASE_4.setIdentifiers(new LinkedList<>(List.of(IDENTIFIER_7))); + DATABASE_4_DTO.setTables(new LinkedList<>(List.of(TABLE_9_DTO))); + DATABASE_4_DTO.setIdentifiers(new LinkedList<>(List.of(IDENTIFIER_7_DTO))); TABLE_9.setDatabase(DATABASE_4); TABLE_9.setColumns(TABLE_9_COLUMNS); TABLE_9.setConstraints(TABLE_9_CONSTRAINTS); TABLE_9_DTO.setColumns(TABLE_9_COLUMNS_DTO); TABLE_9_DTO.setConstraints(TABLE_9_CONSTRAINTS_DTO); - DATABASE_4.setSubsets(new LinkedList<>()); - DATABASE_4.setAccesses(new LinkedList<>(List.of(DATABASE_4_USER_1_READ_ACCESS, DATABASE_4_USER_2_WRITE_OWN_ACCESS, DATABASE_4_USER_3_WRITE_ALL_ACCESS))); - DATABASE_4.setIdentifiers(new LinkedList<>(List.of(IDENTIFIER_7))); IDENTIFIER_7.setStatus(IdentifierStatusType.DRAFT); IDENTIFIER_7.setDatabase(DATABASE_4); } diff --git a/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java b/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java index 368b1d182c..12dbd35efc 100644 --- a/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java +++ b/dbrepo-metadata-service/test/src/main/java/at/tuwien/test/BaseTest.java @@ -11,11 +11,8 @@ import at.tuwien.api.auth.SignupRequestDto; import at.tuwien.api.container.ContainerBriefDto; import at.tuwien.api.container.ContainerDto; import at.tuwien.api.container.image.*; -import at.tuwien.api.container.internal.PrivilegedContainerDto; import at.tuwien.api.database.*; import at.tuwien.api.database.internal.CreateDatabaseDto; -import at.tuwien.api.database.internal.PrivilegedDatabaseDto; -import at.tuwien.api.database.internal.PrivilegedViewDto; import at.tuwien.api.database.query.QueryBriefDto; import at.tuwien.api.database.query.QueryDto; import at.tuwien.api.database.table.TableBriefDto; @@ -29,7 +26,6 @@ import at.tuwien.api.database.table.constraints.ConstraintsDto; import at.tuwien.api.database.table.constraints.foreign.*; import at.tuwien.api.database.table.constraints.primary.PrimaryKeyDto; import at.tuwien.api.database.table.constraints.unique.UniqueDto; -import at.tuwien.api.database.table.internal.PrivilegedTableDto; import at.tuwien.api.datacite.DataCiteBody; import at.tuwien.api.datacite.DataCiteData; import at.tuwien.api.datacite.doi.DataCiteDoi; @@ -53,7 +49,6 @@ import at.tuwien.api.semantics.*; import at.tuwien.api.user.UserAttributesDto; import at.tuwien.api.user.UserDto; import at.tuwien.api.user.*; -import at.tuwien.api.user.internal.PrivilegedUserDto; import at.tuwien.api.user.internal.UpdateUserPasswordDto; import at.tuwien.entities.container.Container; import at.tuwien.entities.container.image.ContainerImage; @@ -553,7 +548,7 @@ public abstract class BaseTest { .build()) .build(); - public final static PrivilegedUserDto USER_1_PRIVILEGED_DTO = PrivilegedUserDto.builder() + public final static UserDto USER_1_PRIVILEGED_DTO = UserDto.builder() .id(USER_1_ID) .username(USER_1_USERNAME) .password(USER_1_PASSWORD) @@ -741,7 +736,7 @@ public abstract class BaseTest { .tags(new String[]{}) .build(); - public final static PrivilegedUserDto USER_2_PRIVILEGED_DTO = PrivilegedUserDto.builder() + public final static UserDto USER_2_PRIVILEGED_DTO = UserDto.builder() .id(USER_2_ID) .username(USER_2_USERNAME) .password(USER_2_PASSWORD) @@ -843,7 +838,7 @@ public abstract class BaseTest { .tags(new String[]{}) .build(); - public final static PrivilegedUserDto USER_3_PRIVILEGED_DTO = PrivilegedUserDto.builder() + public final static UserDto USER_3_PRIVILEGED_DTO = UserDto.builder() .id(USER_3_ID) .username(USER_3_USERNAME) .password(USER_3_PASSWORD) @@ -925,7 +920,7 @@ public abstract class BaseTest { public final static Principal USER_4_PRINCIPAL = new UsernamePasswordAuthenticationToken(USER_4_DETAILS, USER_4_PASSWORD, USER_4_DETAILS.getAuthorities()); - public final static PrivilegedUserDto USER_4_PRIVILEGED_DTO = PrivilegedUserDto.builder() + public final static UserDto USER_4_PRIVILEGED_DTO = UserDto.builder() .id(USER_4_ID) .username(USER_4_USERNAME) .password(USER_4_PASSWORD) @@ -973,7 +968,7 @@ public abstract class BaseTest { .attributes(USER_5_ATTRIBUTES_DTO) .build(); - public final static PrivilegedUserDto USER_5_PRIVILEGED_DTO = PrivilegedUserDto.builder() + public final static UserDto USER_5_PRIVILEGED_DTO = UserDto.builder() .id(USER_5_ID) .username(USER_5_USERNAME) .firstname(USER_5_FIRSTNAME) @@ -1187,7 +1182,7 @@ public abstract class BaseTest { .image(IMAGE_1_BRIEF_DTO) .build(); - public final static PrivilegedContainerDto CONTAINER_1_PRIVILEGED_DTO = PrivilegedContainerDto.builder() + public final static ContainerDto CONTAINER_1_PRIVILEGED_DTO = ContainerDto.builder() .id(CONTAINER_1_ID) .name(CONTAINER_1_NAME) .internalName(CONTAINER_1_INTERNALNAME) @@ -1243,7 +1238,7 @@ public abstract class BaseTest { .quota(CONTAINER_2_QUOTA) .build(); - public final static PrivilegedContainerDto CONTAINER_2_PRIVILEGED_DTO = PrivilegedContainerDto.builder() + public final static ContainerDto CONTAINER_2_PRIVILEGED_DTO = ContainerDto.builder() .id(CONTAINER_2_ID) .name(CONTAINER_2_NAME) .internalName(CONTAINER_2_INTERNALNAME) @@ -1385,11 +1380,21 @@ public abstract class BaseTest { .id(DATABASE_3_ID) .isPublic(DATABASE_3_PUBLIC) .name(DATABASE_3_NAME) - .container(CONTAINER_1_BRIEF_DTO) .internalName(DATABASE_3_INTERNALNAME) + .owner(USER_3_BRIEF_DTO) + .container(CONTAINER_1_DTO) .exchangeName(DATABASE_3_EXCHANGE) - .tables(new LinkedList<>()) /* TABLE_3, TABLE_3, TABLE_3 */ - .views(new LinkedList<>()) + .tables(new LinkedList<>()) /* TABLE_8_DTO */ + .views(new LinkedList<>()) /* VIEW_5_DTO */ + .identifiers(new LinkedList<>()) /* IDENTIFIER_6_DTO */ + .build(); + + public final static DatabaseBriefDto DATABASE_3_BRIEF_DTO = DatabaseBriefDto.builder() + .id(DATABASE_3_ID) + .isPublic(DATABASE_3_PUBLIC) + .name(DATABASE_3_NAME) + .internalName(DATABASE_3_INTERNALNAME) + .ownerId(USER_3_ID) .identifiers(new LinkedList<>()) .build(); @@ -1411,6 +1416,17 @@ public abstract class BaseTest { public final static UUID DATABASE_4_OWNER = USER_4_ID; public final static UUID DATABASE_4_CREATOR = USER_4_ID; + public final static DatabaseBriefDto DATABASE_4_BRIEF_DTO = DatabaseBriefDto.builder() + .id(DATABASE_4_ID) + .isPublic(DATABASE_4_PUBLIC) + .isSchemaPublic(DATABASE_4_SCHEMA_PUBLIC) + .name(DATABASE_4_NAME) + .description(DATABASE_4_DESCRIPTION) + .internalName(DATABASE_4_INTERNALNAME) + .ownerId(USER_4_ID) + .identifiers(new LinkedList<>()) + .build(); + public final static DatabaseDto DATABASE_4_DTO = DatabaseDto.builder() .id(DATABASE_4_ID) .isPublic(DATABASE_4_PUBLIC) @@ -1420,9 +1436,9 @@ public abstract class BaseTest { .internalName(DATABASE_4_INTERNALNAME) .exchangeName(DATABASE_4_EXCHANGE) .owner(USER_4_BRIEF_DTO) - .tables(new LinkedList<>()) + .tables(new LinkedList<>()) /* TABLE_9_DTO */ .views(new LinkedList<>()) - .identifiers(new LinkedList<>()) + .identifiers(new LinkedList<>()) /* IDENTIFIER_7_DTO */ .build(); public final static TableCreateDto TABLE_0_CREATE_DTO = TableCreateDto.builder() @@ -1612,10 +1628,9 @@ public abstract class BaseTest { public final static Instant TABLE_1_CREATED = Instant.ofEpochSecond(1677399975L) /* 2023-02-26 08:26:15 (UTC) */; public final static Instant TABLE_1_LAST_MODIFIED = Instant.ofEpochSecond(1677399975L) /* 2023-02-26 08:26:15 (UTC) */; - public final static PrivilegedTableDto TABLE_1_PRIVILEGED_DTO = PrivilegedTableDto.builder() + public final static TableDto TABLE_1_PRIVILEGED_DTO = TableDto.builder() .id(TABLE_1_ID) .tdbid(DATABASE_1_ID) - .database(null) /* DATABASE_1_PRIVILEGED_DTO */ .internalName(TABLE_1_INTERNAL_NAME) .isVersioned(TABLE_1_VERSIONED) .isPublic(TABLE_1_SCHEMA_PUBLIC) @@ -1826,10 +1841,9 @@ public abstract class BaseTest { .maxDataLength(TABLE_2_MAX_DATA_LENGTH) .build(); - public final static PrivilegedTableDto TABLE_2_PRIVILEGED_DTO = PrivilegedTableDto.builder() + public final static TableDto TABLE_2_PRIVILEGED_DTO = TableDto.builder() .id(TABLE_2_ID) .tdbid(DATABASE_1_ID) - .database(null) /* DATABASE_1_PRIVILEGED_DTO */ .internalName(TABLE_2_INTERNALNAME) .isVersioned(TABLE_2_VERSIONED) .isPublic(TABLE_2_IS_PUBLIC) @@ -2036,10 +2050,9 @@ public abstract class BaseTest { .owner(USER_1_BRIEF_DTO) .build(); - public final static PrivilegedTableDto TABLE_5_PRIVILEGED_DTO = PrivilegedTableDto.builder() + public final static TableDto TABLE_5_PRIVILEGED_DTO = TableDto.builder() .id(TABLE_5_ID) .tdbid(DATABASE_2_ID) - .database(null) /* DATABASE_2_PRIVILEGED_DTO */ .internalName(TABLE_5_INTERNALNAME) .isVersioned(TABLE_5_VERSIONED) .isPublic(TABLE_5_IS_PUBLIC) @@ -2407,7 +2420,7 @@ public abstract class BaseTest { .ownedBy(USER_1_ID) .build(); - public final static PrivilegedTableDto TABLE_8_PRIVILEGED_DTO = PrivilegedTableDto.builder() + public final static TableDto TABLE_8_PRIVILEGED_DTO = TableDto.builder() .id(TABLE_8_ID) .tdbid(TABLE_8_DATABASE_ID) .internalName(TABLE_8_INTERNAL_NAME) @@ -2483,7 +2496,7 @@ public abstract class BaseTest { .ownedBy(USER_1_ID) .build(); - public final static PrivilegedTableDto TABLE_9_PRIVILEGED_DTO = PrivilegedTableDto.builder() + public final static TableDto TABLE_9_PRIVILEGED_DTO = TableDto.builder() .id(TABLE_9_ID) .tdbid(TABLE_9_DATABASE_ID) .internalName(TABLE_9_INTERNAL_NAME) @@ -2979,7 +2992,7 @@ public abstract class BaseTest { .isPersisted(QUERY_3_PERSISTED) .resultNumber(2L) .build(); - + public final static Long QUERY_7_ID = 7L; public final static String QUERY_7_STATEMENT = "SELECT id, date, a.location, lat, lng FROM weather_aus a JOIN weather_location l on a.location = l.location WHERE date = '2008-12-01'"; public final static String QUERY_7_QUERY_HASH = "df7da3801dfb5c191ff6711d79ce6455f3c09ec8323ce1ff7208ab85387263f5"; @@ -5117,10 +5130,9 @@ public abstract class BaseTest { .columns(VIEW_1_COLUMNS_DTO) .build(); - public final static PrivilegedViewDto VIEW_1_PRIVILEGED_DTO = PrivilegedViewDto.builder() + public final static ViewDto VIEW_1_PRIVILEGED_DTO = ViewDto.builder() .id(VIEW_1_ID) .isInitialView(VIEW_1_INITIAL_VIEW) - .database(null) /* DATABASE_1_PRIVILEGED_DTO */ .name(VIEW_1_NAME) .internalName(VIEW_1_INTERNAL_NAME) .vdbid(VIEW_1_DATABASE_ID) @@ -5279,10 +5291,9 @@ public abstract class BaseTest { .owner(USER_1_BRIEF_DTO) .build(); - public final static PrivilegedViewDto VIEW_2_PRIVILEGED_DTO = PrivilegedViewDto.builder() + public final static ViewDto VIEW_2_PRIVILEGED_DTO = ViewDto.builder() .id(VIEW_2_ID) .isInitialView(VIEW_2_INITIAL_VIEW) - .database(null) /* DATABASE_1_PRIVILEGED_DTO */ .name(VIEW_2_NAME) .internalName(VIEW_2_INTERNAL_NAME) .vdbid(VIEW_2_DATABASE_ID) @@ -5380,10 +5391,9 @@ public abstract class BaseTest { .owner(USER_1) .build(); - public final static PrivilegedViewDto VIEW_3_PRIVILEGED_DTO = PrivilegedViewDto.builder() + public final static ViewDto VIEW_3_PRIVILEGED_DTO = ViewDto.builder() .id(VIEW_3_ID) .isInitialView(VIEW_3_INITIAL_VIEW) - .database(null) /* DATABASE_1_PRIVILEGED_DTO */ .name(VIEW_3_NAME) .internalName(VIEW_3_INTERNAL_NAME) .vdbid(VIEW_3_DATABASE_ID) @@ -7518,27 +7528,20 @@ public abstract class BaseTest { .id(DATABASE_1_ID) .isPublic(DATABASE_1_PUBLIC) .name(DATABASE_1_NAME) - .container(CONTAINER_1_BRIEF_DTO) + .container(CONTAINER_1_DTO) .internalName(DATABASE_1_INTERNALNAME) .exchangeName(DATABASE_1_EXCHANGE) - .identifiers(new LinkedList<>(List.of(IDENTIFIER_1_BRIEF_DTO, IDENTIFIER_2_BRIEF_DTO, IDENTIFIER_3_BRIEF_DTO, IDENTIFIER_4_BRIEF_DTO))) - .tables(new LinkedList<>(List.of(TABLE_1_BRIEF_DTO, TABLE_2_BRIEF_DTO, TABLE_3_BRIEF_DTO, TABLE_4_BRIEF_DTO))) - .views(new LinkedList<>(List.of(VIEW_1_BRIEF_DTO, VIEW_2_BRIEF_DTO, VIEW_3_BRIEF_DTO))) + .identifiers(new LinkedList<>(List.of(IDENTIFIER_1_DTO, IDENTIFIER_2_DTO, IDENTIFIER_3_DTO, IDENTIFIER_4_DTO))) + .tables(new LinkedList<>(List.of(TABLE_1_DTO, TABLE_2_DTO, TABLE_3_DTO, TABLE_4_DTO))) + .views(new LinkedList<>(List.of(VIEW_1_DTO, VIEW_2_DTO, VIEW_3_DTO))) .build(); - public final static PrivilegedDatabaseDto DATABASE_1_PRIVILEGED_DTO = PrivilegedDatabaseDto.builder() + public final static DatabaseBriefDto DATABASE_1_BRIEF_DTO = DatabaseBriefDto.builder() .id(DATABASE_1_ID) .isPublic(DATABASE_1_PUBLIC) - .isSchemaPublic(DATABASE_1_SCHEMA_PUBLIC) .name(DATABASE_1_NAME) - .container(CONTAINER_1_PRIVILEGED_DTO) .internalName(DATABASE_1_INTERNALNAME) - .exchangeName(DATABASE_1_EXCHANGE) - .identifiers(new LinkedList<>(List.of(IDENTIFIER_1_DTO, IDENTIFIER_2_DTO, IDENTIFIER_3_DTO, IDENTIFIER_4_DTO))) - .tables(new LinkedList<>(List.of(TABLE_1_DTO, TABLE_2_DTO, TABLE_3_DTO, TABLE_4_DTO))) - .views(new LinkedList<>(List.of(VIEW_1_DTO, VIEW_2_DTO, VIEW_3_DTO))) - .owner(USER_1_BRIEF_DTO) - .lastRetrieved(Instant.now()) + .identifiers(new LinkedList<>(List.of(IDENTIFIER_1_BRIEF_DTO, IDENTIFIER_2_BRIEF_DTO, IDENTIFIER_3_BRIEF_DTO, IDENTIFIER_4_BRIEF_DTO))) .build(); public final static DatabaseAccess DATABASE_1_USER_1_READ_ACCESS = DatabaseAccess.builder() @@ -7672,7 +7675,7 @@ public abstract class BaseTest { .identifiers(new LinkedList<>()) .build(); - public final static PrivilegedDatabaseDto DATABASE_2_PRIVILEGED_DTO = PrivilegedDatabaseDto.builder() + public final static DatabaseDto DATABASE_2_DTO = DatabaseDto.builder() .id(DATABASE_2_ID) .isPublic(DATABASE_2_PUBLIC) .isSchemaPublic(DATABASE_2_SCHEMA_PUBLIC) @@ -7687,18 +7690,13 @@ public abstract class BaseTest { .lastRetrieved(Instant.now()) .build(); - public final static DatabaseDto DATABASE_2_DTO = DatabaseDto.builder() + public final static DatabaseBriefDto DATABASE_2_BRIEF_DTO = DatabaseBriefDto.builder() .id(DATABASE_2_ID) .isPublic(DATABASE_2_PUBLIC) .name(DATABASE_2_NAME) - .container(CONTAINER_1_BRIEF_DTO) .internalName(DATABASE_2_INTERNALNAME) - .exchangeName(DATABASE_2_EXCHANGE) .identifiers(new LinkedList<>(List.of(IDENTIFIER_5_BRIEF_DTO))) - .tables(new LinkedList<>(List.of(TABLE_5_BRIEF_DTO, TABLE_6_BRIEF_DTO, TABLE_7_BRIEF_DTO))) - .views(new LinkedList<>(List.of(VIEW_4_BRIEF_DTO))) - .identifiers(new LinkedList<>()) - .owner(USER_2_BRIEF_DTO) + .ownerId(USER_2_ID) .build(); public final static DatabaseAccess DATABASE_2_USER_1_READ_ACCESS = DatabaseAccess.builder() @@ -7924,21 +7922,6 @@ public abstract class BaseTest { .user(USER_3_BRIEF_DTO) .build(); - public final static PrivilegedDatabaseDto DATABASE_3_PRIVILEGED_DTO = PrivilegedDatabaseDto.builder() - .id(DATABASE_3_ID) - .isPublic(DATABASE_3_PUBLIC) - .isSchemaPublic(DATABASE_3_SCHEMA_PUBLIC) - .name(DATABASE_3_NAME) - .container(CONTAINER_1_PRIVILEGED_DTO) - .internalName(DATABASE_3_INTERNALNAME) - .exchangeName(DATABASE_3_EXCHANGE) - .identifiers(new LinkedList<>(List.of(IDENTIFIER_6_DTO))) - .tables(new LinkedList<>(List.of(TABLE_8_DTO))) - .views(new LinkedList<>(List.of(VIEW_5_DTO))) - .owner(USER_3_BRIEF_DTO) - .lastRetrieved(Instant.now()) - .build(); - public final static Identifier IDENTIFIER_7 = Identifier.builder() .id(IDENTIFIER_7_ID) .descriptions(new LinkedList<>()) @@ -7985,21 +7968,6 @@ public abstract class BaseTest { .identifiers(new LinkedList<>()) .build(); - public final static PrivilegedDatabaseDto DATABASE_4_PRIVILEGED_DTO = PrivilegedDatabaseDto.builder() - .id(DATABASE_4_ID) - .isPublic(DATABASE_4_PUBLIC) - .isSchemaPublic(DATABASE_4_SCHEMA_PUBLIC) - .name(DATABASE_4_NAME) - .container(CONTAINER_1_PRIVILEGED_DTO) - .internalName(DATABASE_4_INTERNALNAME) - .exchangeName(DATABASE_4_EXCHANGE) - .identifiers(new LinkedList<>(List.of(IDENTIFIER_7_DTO))) - .tables(new LinkedList<>(List.of(TABLE_9_DTO))) - .views(new LinkedList<>(List.of())) - .owner(USER_3_BRIEF_DTO) - .lastRetrieved(Instant.now()) - .build(); - public final static DatabaseAccess DATABASE_4_USER_1_READ_ACCESS = DatabaseAccess.builder() .type(AccessType.READ) .hdbid(DATABASE_4_ID) diff --git a/dbrepo-search-service/Pipfile.lock b/dbrepo-search-service/Pipfile.lock index c789904077..8362cd2df3 100644 --- a/dbrepo-search-service/Pipfile.lock +++ b/dbrepo-search-service/Pipfile.lock @@ -360,7 +360,7 @@ }, "dbrepo": { "hashes": [ - "sha256:501b53c7e4b32774809f9685a18288da5b938fc1512e94d8b248f531ee8667fc" + "sha256:19c6bbcf9461e20681f0fb342087c618a91123d2d04d4df2f4fd1da80aa77b76" ], "path": "./lib/dbrepo-1.6.2.tar.gz" }, diff --git a/dbrepo-search-service/init/Pipfile.lock b/dbrepo-search-service/init/Pipfile.lock index 64f5fc6cc6..e72262e85d 100644 --- a/dbrepo-search-service/init/Pipfile.lock +++ b/dbrepo-search-service/init/Pipfile.lock @@ -254,10 +254,9 @@ }, "dbrepo": { "hashes": [ - "sha256:501b53c7e4b32774809f9685a18288da5b938fc1512e94d8b248f531ee8667fc" + "sha256:19c6bbcf9461e20681f0fb342087c618a91123d2d04d4df2f4fd1da80aa77b76" ], - "path": "./lib/dbrepo-1.6.2.tar.gz", - "version": "==1.6.2" + "path": "./lib/dbrepo-1.6.2.tar.gz" }, "docker": { "hashes": [ @@ -279,6 +278,7 @@ "sha256:f69fcd559dc907ed196ab9df0e48471709175e696d6e698dd4dbe940f96ce66b" ], "index": "pypi", + "markers": "python_version >= '3.8'", "version": "==2.3.3" }, "frozenlist": { @@ -643,6 +643,7 @@ "sha256:6598df0bc7a003294edd0ba88a331e0793acbb8c910c43edf398791e3b2eccda" ], "index": "pypi", + "markers": "python_version >= '3.8' and python_version < '4'", "version": "==2.8.0" }, "packaging": { @@ -933,6 +934,7 @@ "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761" ], "index": "pypi", + "markers": "python_version >= '3.8'", "version": "==8.3.4" }, "python-dateutil": { @@ -940,7 +942,7 @@ "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==2.9.0.post0" }, "python-dotenv": { @@ -949,6 +951,7 @@ "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a" ], "index": "pypi", + "markers": "python_version >= '3.8'", "version": "==1.0.1" }, "pytz": { @@ -964,6 +967,7 @@ "sha256:f3dcb4c106a8cd9e060d92f43d593d09ebc3d07adc244f4c7315856a12e383ee" ], "index": "pypi", + "markers": "python_full_version >= '3.8.1' and python_full_version < '4.0.0'", "version": "==7.1.3" }, "requests": { @@ -979,7 +983,7 @@ "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==1.17.0" }, "testcontainers-core": { @@ -994,6 +998,7 @@ "sha256:0bdf270b5b7f53915832f7c31dd2bd3ffdc20b534ea6b32231cc7003049bd0e1" ], "index": "pypi", + "markers": "python_version >= '3.7'", "version": "==0.0.1rc1" }, "tinydb": { @@ -1285,6 +1290,7 @@ "sha256:fd34e7b3405f0cc7ab03d54a334c17a9e802897580d964bd8c2001f4b9fd488f" ], "index": "pypi", + "markers": "python_version >= '3.9'", "version": "==7.6.10" }, "iniconfig": { @@ -1317,6 +1323,7 @@ "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761" ], "index": "pypi", + "markers": "python_version >= '3.8'", "version": "==8.3.4" } } diff --git a/dbrepo-search-service/init/lib/dbrepo-1.6.2.tar.gz b/dbrepo-search-service/init/lib/dbrepo-1.6.2.tar.gz index 58081673e955d89fccf70c9161037a725b647f71..02ed2aec31c2b1881165a12d45060ed4a311192d 100644 GIT binary patch delta 39303 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 R=>>ny-)xbb%dmiz0RTX?Dir_# delta 39288 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 LHtuT-3s@NdgDHfU diff --git a/dbrepo-search-service/lib/dbrepo-1.6.2.tar.gz b/dbrepo-search-service/lib/dbrepo-1.6.2.tar.gz index 58081673e955d89fccf70c9161037a725b647f71..02ed2aec31c2b1881165a12d45060ed4a311192d 100644 GIT binary patch delta 39303 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 R=>>ny-)xbb%dmiz0RTX?Dir_# delta 39288 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 LHtuT-3s@NdgDHfU diff --git a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/settings.vue b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/settings.vue index 3d718d5739..f953071136 100644 --- a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/settings.vue +++ b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/settings.vue @@ -24,7 +24,7 @@ v-model="modify.description" rows="2" :rules="[ - v => !max(v, 180) || ($t('validation.max-length') + 180), + v => max(v, 180) || ($t('validation.max-length') + 180), ]" clearable counter="180" diff --git a/lib/python/dbrepo/api/dto.py b/lib/python/dbrepo/api/dto.py index fa7eb063fc..a8518ec79e 100644 --- a/lib/python/dbrepo/api/dto.py +++ b/lib/python/dbrepo/api/dto.py @@ -511,6 +511,7 @@ class CreateTableColumn(BaseModel): name: str type: ColumnType null_allowed: bool + description: Optional[str] = None concept_uri: Optional[str] = None unit_uri: Optional[str] = None index_length: Optional[int] = None @@ -622,6 +623,21 @@ class Identifier(BaseModel): publication_month: Optional[int] = None +class IdentifierBrief(BaseModel): + id: int + database_id: int + type: IdentifierType + created_by: str + status: IdentifierStatusType + publication_year: int + publisher: str + titles: List[IdentifierTitle] + doi: Optional[str] = None + query_id: Optional[int] = None + table_id: Optional[int] = None + view_id: Optional[int] = None + + class View(BaseModel): id: int database_id: int @@ -961,7 +977,7 @@ class ColumnMinimal(BaseModel): database_id: int -class Database(BaseModel): +class DatabaseBrief(BaseModel): id: int name: str owner: UserBrief @@ -971,26 +987,36 @@ class Database(BaseModel): is_public: bool is_schema_public: bool container: ContainerBrief - identifiers: Optional[List[Identifier]] = field(default_factory=list) - subsets: Optional[List[Identifier]] = field(default_factory=list) + identifiers: Optional[List[IdentifierBrief]] = field(default_factory=list) + subsets: Optional[List[IdentifierBrief]] = field(default_factory=list) + preview_image: Optional[str] = None description: Optional[str] = None - tables: Optional[List[Table]] = field(default_factory=list) - views: Optional[List[View]] = field(default_factory=list) + tables: Optional[List[TableBrief]] = field(default_factory=list) + views: Optional[List[ViewBrief]] = field(default_factory=list) image: Optional[str] = None accesses: Optional[List[DatabaseAccess]] = field(default_factory=list) - exchange_type: Optional[str] = None + exchange_name: Optional[str] = None -class DatabaseBrief(BaseModel): +class Database(BaseModel): id: int name: str + owner: UserBrief + contact: UserBrief + exchange_name: str internal_name: str - description: Optional[str] = None is_public: bool is_schema_public: bool + container: ContainerBrief identifiers: Optional[List[Identifier]] = field(default_factory=list) - contact: UserBrief - owner_id: str + subsets: Optional[List[Identifier]] = field(default_factory=list) + preview_image: Optional[str] = None + description: Optional[str] = None + tables: Optional[List[Table]] = field(default_factory=list) + views: Optional[List[View]] = field(default_factory=list) + image: Optional[str] = None + accesses: Optional[List[DatabaseAccess]] = field(default_factory=list) + exchange_name: Optional[str] = None class Unique(BaseModel): -- GitLab