diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/query/QueryResultDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/query/QueryResultDto.java index ac21249ec5f26fe582c2f48e11c99dc800b9e7a9..d64bfd4db378c5807cdc68cf1db3419201fc7b22 100644 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/query/QueryResultDto.java +++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/query/QueryResultDto.java @@ -22,6 +22,9 @@ public class QueryResultDto { @NotNull(message = "result set is required") private List<Map<String, Object>> result; + @NotNull(message = "headers is required") + private List<Map<String, Integer>> headers; + @NotNull(message = "query id is required") private Long id; diff --git a/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/QueryMapper.java b/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/QueryMapper.java index d25717e5034620624ad16eb8486d6ee739afcae6..d4d1fb168ab8bdf0fa03d361b5467cb4809d9598 100644 --- a/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/QueryMapper.java +++ b/dbrepo-metadata-service/repositories/src/main/java/at/tuwien/mapper/QueryMapper.java @@ -106,8 +106,16 @@ public interface QueryMapper { } resultList.add(map); } + final int[] idx = new int[]{0}; + final List<Map<String, Integer>> headers = columns.stream() + .map(c -> (Map<String, Integer>) new LinkedHashMap<String, Integer>(){{ + put(c.getAlias() != null ? c.getAlias() : c.getInternalName(), idx[0]++); + }}) + .toList(); + log.debug("created ordered header list: {}", headers); return QueryResultDto.builder() .result(resultList) + .headers(headers) .build(); } diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/QueryEndpoint.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/QueryEndpoint.java index 65f4ca6ff63c1e92e4df314b1106ea3437649120..bd494caa52da8176cfaa2c5aaf859ac43c3c78c5 100644 --- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/QueryEndpoint.java +++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/QueryEndpoint.java @@ -101,8 +101,8 @@ public class QueryEndpoint { endpointValidator.validateOnlyAccessOrPublic(databaseId, queryId, principal); /* execute */ final Query query = storeService.findOne(databaseId, queryId, principal); - final QueryResultDto result = queryService.reExecute(databaseId, query, page, size, - sortDirection, sortColumn, principal); + final QueryResultDto result = queryService.reExecute(databaseId, query, page, size, sortDirection, sortColumn, + principal); result.setId(queryId); log.trace("re-execute query resulted in result {}", result); return ResponseEntity.status(HttpStatus.ACCEPTED) diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/QueryServiceIntegrationTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/QueryServiceIntegrationTest.java index 47085ed2da077fb0d8da36d7b0d6a204daee6bfe..5469cb51070e0e71878a3e7f6ce8cacbe0233d1b 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/QueryServiceIntegrationTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/service/QueryServiceIntegrationTest.java @@ -301,8 +301,10 @@ public class QueryServiceIntegrationTest extends BaseUnitTest { .statement("SELECT n.`firstname`, n.`lastname`, n.`birth`, n.`reminder`, z.`animal_name`, z.`legs` FROM `likes` l JOIN `names` n ON l.`name_id` = n.`id` JOIN `mock_view` z ON z.`id` = l.`zoo_id`") .build(); - /* test */ + /* pre-condition */ Thread.sleep(1000) /* wait for test container some more */; + + /* test */ final QueryResultDto response = queryService.execute(DATABASE_2_ID, request, USER_1_PRINCIPAL, 0L, 100L, null, null); assertEquals(4L, response.getResultNumber()); assertNotNull(response.getResult()); @@ -338,8 +340,10 @@ public class QueryServiceIntegrationTest extends BaseUnitTest { .statement("SELECT `location`, `lng` FROM `weather_location` WHERE `lat` IS NULL") .build(); - /* test */ + /* pre-condition */ Thread.sleep(1000) /* wait for test container some more */; + + /* test */ final QueryResultDto response = queryService.execute(DATABASE_1_ID, request, USER_1_PRINCIPAL, 0L, 100L, null, null); assertEquals(1L, response.getResultNumber()); @@ -358,8 +362,10 @@ public class QueryServiceIntegrationTest extends BaseUnitTest { .statement("SELECT `location` FROM `weather_location` WHERE `lat` IS NULL") .build(); - /* test */ + /* pre-condition */ Thread.sleep(1000) /* wait for test container some more */; + + /* test */ final QueryResultDto response = queryService.execute(DATABASE_1_ID, request, USER_1_PRINCIPAL, 0L, 100L, null, null); assertEquals(1L, response.getResultNumber()); @@ -379,8 +385,10 @@ public class QueryServiceIntegrationTest extends BaseUnitTest { .statement("SELECT `lat`, `lng` FROM `weather_location` WHERE `lat` IS NULL") .build(); - /* test */ + /* pre-condition */ Thread.sleep(1000) /* wait for test container some more */; + + /* test */ final QueryResultDto response = queryService.execute(DATABASE_1_ID, request, USER_1_PRINCIPAL, 0L, 100L, null, null); assertEquals(1L, response.getResultNumber()); @@ -395,8 +403,10 @@ public class QueryServiceIntegrationTest extends BaseUnitTest { .statement("SELECT aus.location as a, loc.location from weather_aus aus, weather_location loc") .build(); - /* test */ + /* pre-condition */ Thread.sleep(1000) /* wait for test container some more */; + + /* test */ final QueryResultDto response = queryService.execute(DATABASE_1_ID, request, USER_1_PRINCIPAL, 0L, 100L, null, null); assertEquals(9L, response.getResultNumber()); assertNotNull(response.getResult()); @@ -430,7 +440,7 @@ public class QueryServiceIntegrationTest extends BaseUnitTest { .statement("SELECT aus.location as a, loc.location from weather.weather_aus aus, weather.weather_location loc") .build(); - /* mock */ + /* pre-condition */ Thread.sleep(1000) /* wait for test container some more */; /* test */ @@ -464,13 +474,14 @@ public class QueryServiceIntegrationTest extends BaseUnitTest { DatabaseNotFoundException, ImageNotSupportedException, QueryMalformedException, UserNotFoundException, InterruptedException, ViewMalformedException, PaginationException, ViewNotFoundException { - /* mock */ + /* pre-condition */ Thread.sleep(1000) /* wait for test container some more */; /* test */ final QueryResultDto response = queryService.viewFindAll(DATABASE_1_ID, VIEW_2, 0L, 10L, USER_1_PRINCIPAL); assertNotNull(response.getResult()); final List<Map<String, Object>> result = response.getResult(); + /* values */ assertEquals(0.6, result.get(0).get("rainfall")); assertEquals("Albury", result.get(0).get("loc")); assertEquals(13.4, result.get(0).get("mintemp")); @@ -480,6 +491,12 @@ public class QueryServiceIntegrationTest extends BaseUnitTest { assertEquals(0.0, result.get(2).get("rainfall")); assertEquals("Albury", result.get(2).get("loc")); assertEquals(12.9, result.get(2).get("mintemp")); + /* ordering */ + final String[] keys = result.get(0).keySet().toArray(new String[0]); + assertEquals("date", keys[0]); + assertEquals("loc", keys[1]); + assertEquals("rainfall", keys[2]); + assertEquals("mintemp", keys[3]); } @Test diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/QueryService.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/QueryService.java index aea4cd4c40a4aaf308c37e0ffd8921be540da6f3..534b30c69b1007dbed44cb14813b4cc34d84e572 100644 --- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/QueryService.java +++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/QueryService.java @@ -66,10 +66,10 @@ public interface QueryService { * @throws ColumnParseException The column mapping/parsing failed. * @throws QueryMalformedException The query is malformed. */ - QueryResultDto reExecute(Long databaseId, Query query, Long page, Long size, - SortType sortDirection, String sortColumn, Principal principal) - throws QueryMalformedException, DatabaseNotFoundException, ImageNotSupportedException, ColumnParseException, - DatabaseConnectionException, TableMalformedException, QueryStoreException, UserNotFoundException; + QueryResultDto reExecute(Long databaseId, Query query, Long page, Long size, SortType sortDirection, + String sortColumn, Principal principal) throws QueryMalformedException, + DatabaseNotFoundException, ImageNotSupportedException, ColumnParseException, DatabaseConnectionException, + TableMalformedException, QueryStoreException, UserNotFoundException; /** * Re-Executes the count-statement of an arbitrary query on the database. We allow the user to only view diff --git a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java index fcde64dccef014d9a6ec4c42a8a0795e3e457804..074737f8bfd45ff0816d9be733e21c8b923468b8 100644 --- a/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java +++ b/dbrepo-metadata-service/services/src/main/java/at/tuwien/service/impl/QueryServiceImpl.java @@ -503,7 +503,7 @@ public class QueryServiceImpl extends HibernateConnector implements QueryService } @Transactional(readOnly = true) - protected boolean columnMatches(TableColumn column, String tableOrView) { + public boolean columnMatches(TableColumn column, String tableOrView) { if (column.getTable().getInternalName().equals(tableOrView)) { /* matches table name */ return true; 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 39d7f78e6d17340c4989f4bcb30309cec98ef198..13b181972e95832f35534de480a49ed0b1bcf058 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 @@ -614,7 +614,7 @@ public abstract class BaseTest { .affiliation(USER_5_AFFILIATION) .themeDark(USER_5_THEME_DARK) .mariadbPassword(USER_5_DATABASE_PASSWORD) - .build(); + .build(); public final static UUID USER_6_ID = UUID.fromString("28ff851d-d7bc-4422-959c-edd7a5b15630"); public final static String USER_6_USERNAME = "system"; @@ -3115,8 +3115,8 @@ public abstract class BaseTest { public final static Long QUERY_3_ID = 3L; public final static String QUERY_3_STATEMENT = "SELECT `location`, `mintemp` FROM `weather_aus` WHERE `mintemp` > 10"; public final static String QUERY_3_QUERY_HASH = "a3d3dd94ebc7653bb5a3b55dd8ed5e91d3d13c335c6855a1eb4eb7ca14c36ced"; - public final static Long QUERY_3_CONTAINER_ID = CONTAINER_2_ID; - public final static Long QUERY_3_DATABASE_ID = DATABASE_2_ID; + public final static Long QUERY_3_CONTAINER_ID = CONTAINER_1_ID; + public final static Long QUERY_3_DATABASE_ID = DATABASE_1_ID; public final static String QUERY_3_RESULT_HASH = "ff3f7cbe1b96d396957f6e39e55b8b1b577fa3d305d4795af99594cfd30cb80d"; public final static Instant QUERY_3_CREATED = Instant.now().minus(3, MINUTES); public final static Instant QUERY_3_EXECUTION = Instant.now().minus(1, MINUTES); @@ -5078,8 +5078,8 @@ public abstract class BaseTest { public final static Long VIEW_2_CONTAINER_ID = CONTAINER_1_ID; public final static Long VIEW_2_DATABASE_ID = DATABASE_1_ID; public final static Boolean VIEW_2_PUBLIC = true; - public final static String VIEW_2_QUERY = "select `date`, `location` as loc, `mintemp`, `rainfall` from `weather_aus` where `location` = 'Albury'"; - public final static String VIEW_2_QUERY_HASH = "c76efcbab7e117ed286fd6c0f766178727debfeb5544633f8fbfaf2230340d17"; + public final static String VIEW_2_QUERY = "select `date`, `location` as loc, `rainfall`, `mintemp` from `weather_aus` where `location` = 'Albury'"; + public final static String VIEW_2_QUERY_HASH = "987fc946772ffb6d85060262dcb5df419692a1f6772ea995e3dedb53c191e984"; public final static List<TableColumn> VIEW_2_COLUMNS = List.of(TableColumn.builder() .id(COLUMN_1_2_ID) @@ -5109,19 +5109,6 @@ public abstract class BaseTest { .enums(COLUMN_1_3_ENUM_VALUES) .sets(COLUMN_1_3_SET_VALUES) .build(), - TableColumn.builder() - .id(COLUMN_1_4_ID) - .ordinalPosition(COLUMN_1_4_ORDINALPOS) - .table(TABLE_1) - .name(COLUMN_1_4_NAME) - .internalName(COLUMN_1_4_INTERNAL_NAME) - .columnType(COLUMN_1_4_TYPE) - .isNullAllowed(COLUMN_1_4_NULL) - .autoGenerated(COLUMN_1_4_AUTO_GENERATED) - .isPrimaryKey(COLUMN_1_4_PRIMARY) - .enums(COLUMN_1_4_ENUM_VALUES) - .sets(COLUMN_1_4_SET_VALUES) - .build(), TableColumn.builder() .id(COLUMN_1_5_ID) .ordinalPosition(COLUMN_1_5_ORDINALPOS) @@ -5134,6 +5121,19 @@ public abstract class BaseTest { .isPrimaryKey(COLUMN_1_5_PRIMARY) .enums(COLUMN_1_5_ENUM_VALUES) .sets(COLUMN_1_5_SET_VALUES) + .build(), + TableColumn.builder() + .id(COLUMN_1_4_ID) + .ordinalPosition(COLUMN_1_4_ORDINALPOS) + .table(TABLE_1) + .name(COLUMN_1_4_NAME) + .internalName(COLUMN_1_4_INTERNAL_NAME) + .columnType(COLUMN_1_4_TYPE) + .isNullAllowed(COLUMN_1_4_NULL) + .autoGenerated(COLUMN_1_4_AUTO_GENERATED) + .isPrimaryKey(COLUMN_1_4_PRIMARY) + .enums(COLUMN_1_4_ENUM_VALUES) + .sets(COLUMN_1_4_SET_VALUES) .build()); public final static View VIEW_2 = View.builder() @@ -5378,7 +5378,7 @@ public abstract class BaseTest { .isPrimaryKey(COLUMN_4_7_PRIMARY) .enums(COLUMN_4_7_ENUM_VALUES) .sets(COLUMN_4_7_SET_VALUES) - .build(), + .build(), TableColumn.builder() .id(COLUMN_4_9_ID) .ordinalPosition(COLUMN_4_9_ORDINALPOS) diff --git a/dbrepo-ui/components/query/Results.vue b/dbrepo-ui/components/query/Results.vue index 60f6eaabed62610bc31f4366412f0a540b6da9ec..3249f99e18ba8a3889862865a95024fd48f16ff5 100644 --- a/dbrepo-ui/components/query/Results.vue +++ b/dbrepo-ui/components/query/Results.vue @@ -80,13 +80,6 @@ export default { this.loading-- }) }, - buildHeaders (firstLine) { - return Object.keys(firstLine).map(k => ({ - text: k, - value: k, - sortable: false - })) - }, reExecute (id) { if (id === null) { return @@ -136,9 +129,13 @@ export default { } }, mapResults (data) { - if (data.result.length) { - this.result.headers = this.buildHeaders(data.result[0]) - } + this.result.headers = data.headers.map((h) => { + return { + text: Object.keys(h)[0], + value: Object.keys(h)[0], + sortable: false + } + }) console.debug('query result', data) this.result.rows = data.result if (this.total < 0 && data.result_number != null) {