From 7df2d8f6296b97e3005060f7e35fc102255cb741 Mon Sep 17 00:00:00 2001 From: Martin Weise <martin.weise@tuwien.ac.at> Date: Fri, 13 Dec 2024 07:26:21 +0000 Subject: [PATCH 1/5] Resolve "Hotfix query execution" --- .docs/api/data-service.md | 15 + .docs/api/ui.md | 3 +- .docs/changelog.md | 11 + .docs/index.md | 2 +- .docs/kubernetes.md | 2 +- .gitlab-ci.yml | 4 +- Makefile | 4 +- dbrepo-analyse-service/Pipfile | 2 +- dbrepo-analyse-service/Pipfile.lock | 636 +++++++++--------- .../lib/dbrepo-1.5.3.tar.gz | Bin 0 -> 39620 bytes dbrepo-data-service/pom.xml | 12 +- dbrepo-data-service/querystore/pom.xml | 4 +- dbrepo-data-service/report/pom.xml | 4 +- dbrepo-data-service/rest-service/pom.xml | 6 +- .../at/tuwien/endpoints/AbstractEndpoint.java | 34 + .../at/tuwien/endpoints/SubsetEndpoint.java | 116 ++-- .../at/tuwien/endpoints/TableEndpoint.java | 53 +- .../at/tuwien/endpoints/ViewEndpoint.java | 65 +- .../src/main/resources/application.yml | 23 +- .../java/at/tuwien/annotations/MockAmqp.java | 17 - .../endpoint/SubsetEndpointUnitTest.java | 204 ++++-- .../endpoint/TableEndpointUnitTest.java | 86 +-- .../tuwien/endpoint/ViewEndpointUnitTest.java | 167 +++-- .../MetadataServiceGatewayUnitTest.java | 83 +-- .../tuwien/mvc/ActuatorEndpointMvcTest.java | 2 - .../at/tuwien/mvc/OpenApiEndpointMvcTest.java | 1 - .../tuwien/mvc/PrometheusEndpointMvcTest.java | 2 +- .../at/tuwien/mvc/SubsetEndpointMvcTest.java | 2 - .../service/SchemaServiceIntegrationTest.java | 8 +- .../service/SubsetServiceIntegrationTest.java | 186 ----- .../service/TableServiceIntegrationTest.java | 90 +-- .../service/ViewServiceIntegrationTest.java | 35 +- .../validation/EndpointValidatorUnitTest.java | 6 - .../src/test/resources/init/weather.sql | 19 +- dbrepo-data-service/services/pom.xml | 6 +- .../gateway/MetadataServiceGateway.java | 25 - .../impl/MetadataServiceGatewayImpl.java | 66 +- .../java/at/tuwien/mapper/DataMapper.java | 258 ------- .../java/at/tuwien/mapper/MariaDbMapper.java | 106 +-- .../java/at/tuwien/mapper/MetadataMapper.java | 5 + .../java/at/tuwien/service/SubsetService.java | 83 +-- .../java/at/tuwien/service/TableService.java | 45 +- .../java/at/tuwien/service/ViewService.java | 67 +- .../impl/AccessServiceMariaDbImpl.java | 14 +- .../impl/DatabaseServiceMariaDbImpl.java | 4 +- .../service/impl/HibernateConnector.java | 33 - .../impl/QueueServiceRabbitMqImpl.java | 2 +- .../impl/SchemaServiceMariaDbImpl.java | 12 +- .../impl/SubsetServiceMariaDbImpl.java | 182 +---- .../service/impl/TableServiceMariaDbImpl.java | 91 +-- .../service/impl/ViewServiceMariaDbImpl.java | 153 ++--- dbrepo-metadata-service/api/pom.xml | 6 +- .../at/tuwien/api/database/ViewCreateDto.java | 2 +- .../api/database/query/QueryResultDto.java | 29 - dbrepo-metadata-service/entities/pom.xml | 4 +- dbrepo-metadata-service/oai/pom.xml | 4 +- dbrepo-metadata-service/pom.xml | 9 +- dbrepo-metadata-service/report/pom.xml | 4 +- dbrepo-metadata-service/repositories/pom.xml | 4 +- dbrepo-metadata-service/rest-service/pom.xml | 4 +- .../src/main/resources/application.yml | 30 +- .../tuwien/mapper/MetadataMapperUnitTest.java | 2 +- ...nticationPrivilegedIntegrationMvcTest.java | 4 +- .../tuwien/service/TableServiceUnitTest.java | 2 +- dbrepo-metadata-service/services/pom.xml | 4 +- dbrepo-metadata-service/test/pom.xml | 4 +- .../java/at/tuwien/test/AbstractUnitTest.java | 1 + .../main/java/at/tuwien/test/BaseTest.java | 247 ++++--- dbrepo-search-service/Pipfile | 2 +- dbrepo-search-service/Pipfile.lock | 614 +++++++++-------- dbrepo-search-service/init/Pipfile.lock | 398 +++++------ .../init/lib/dbrepo-1.5.3.tar.gz | Bin 0 -> 39620 bytes dbrepo-search-service/lib/dbrepo-1.5.3.tar.gz | Bin 0 -> 39620 bytes dbrepo-ui/components/subset/Results.vue | 6 +- dbrepo-ui/composables/query-service.ts | 14 +- dbrepo-ui/composables/table-service.ts | 7 +- dbrepo-ui/composables/view-service.ts | 7 +- dbrepo-ui/dto/index.ts | 2 +- dbrepo-ui/locales/en-US.json | 4 +- .../[database_id]/subset/[subset_id]/data.vue | 2 +- .../[database_id]/subset/[subset_id]/info.vue | 2 +- .../[database_id]/table/[table_id]/info.vue | 2 +- dbrepo-upload-service/pom.xml | 8 +- .../src/main/resources/application.yml | 48 +- .../src/test/resources/application.properties | 11 +- helm/dbrepo/Chart.yaml | 4 +- helm/dbrepo/README.md | 4 +- helm/dbrepo/values.yaml | 14 +- install.sh | 2 +- lib/python/pyproject.toml | 2 +- lib/python/setup.py | 2 +- sonar-project.properties | 2 +- 92 files changed, 1761 insertions(+), 2812 deletions(-) create mode 100644 dbrepo-analyse-service/lib/dbrepo-1.5.3.tar.gz create mode 100644 dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/AbstractEndpoint.java delete mode 100644 dbrepo-data-service/rest-service/src/test/java/at/tuwien/annotations/MockAmqp.java delete mode 100644 dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/query/QueryResultDto.java create mode 100644 dbrepo-search-service/init/lib/dbrepo-1.5.3.tar.gz create mode 100644 dbrepo-search-service/lib/dbrepo-1.5.3.tar.gz diff --git a/.docs/api/data-service.md b/.docs/api/data-service.md index 64f0397613..257c68c195 100644 --- a/.docs/api/data-service.md +++ b/.docs/api/data-service.md @@ -28,8 +28,23 @@ The Data Service is responsible for inserting AMQP tuples from the Broker Servic via [Spring AMQP](https://docs.spring.io/spring-amqp/reference/html/). To increase the number of consumers, scale the Data Service up. +## Data Processing + +The Data Service uses [Apache Spark](https://spark.apache.org/), a data engine to load data from/into +the [Data Database](../data-db) with a wide range of open-source connectors. The default deployment uses a local mode of +embedded processing directly in the service until there exists +a [Bitnami Chart](https://artifacthub.io/packages/helm/bitnami/spark) for Spark 4. + +Retrieving data from a subset internally generates a view with the 64-character hash of the query. This view is not +automatically deleted currently. + ## Limitations +* Views in DBRepo can only have 63-character length (it is assumed only internal views have the maximum length of 64 + characters). +* Local mode of embedded processing of Apache Spark directly in the service using + a [`local[2]`](https://spark.apache.org/docs/latest/#running-the-examples-and-shell) configuration. + !!! question "Do you miss functionality? Do these limitations affect you?" We strongly encourage you to help us implement it as we are welcoming contributors to open-source software and get diff --git a/.docs/api/ui.md b/.docs/api/ui.md index 08e21b1c26..30b32c0a0c 100644 --- a/.docs/api/ui.md +++ b/.docs/api/ui.md @@ -101,7 +101,8 @@ See the [API Overview](..) page for detailed examples. ## Limitations -(none) +* When developing locally, the `axios` module does not parse custom headers (such as `X-Count`, `X-Headers`) and/or + blocks CORS requests wrongfully. !!! question "Do you miss functionality? Do these limitations affect you?" diff --git a/.docs/changelog.md b/.docs/changelog.md index 98a65bf4ce..429f6933b6 100644 --- a/.docs/changelog.md +++ b/.docs/changelog.md @@ -2,6 +2,17 @@ author: Martin Weise --- +## v1.5.3 (2024-12-13) + +[:simple-gitlab: GitLab Release](https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services/-/tags/v1.5.3) + +### What's Changed + +#### Fixes + +* Fixed a bug where subsets containing sub-queries are not able to retrieve data + in [#476](https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services/-/issues/476). + ## v1.5.2 (2024-12-03) [:simple-gitlab: GitLab Release](https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services/-/tags/v1.5.2) diff --git a/.docs/index.md b/.docs/index.md index 8265da0102..d99d08d358 100644 --- a/.docs/index.md +++ b/.docs/index.md @@ -14,7 +14,7 @@ author: Martin Weise   -Documentation for version: [v1.5.2](https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services/-/releases). +Documentation for version: [v1.5.3](https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services/-/releases). DBRepo is a repository for data in databases that are used from the beginning until the end of a research project supporting data evolution, -citation and -versioning. It implements the query store of the diff --git a/.docs/kubernetes.md b/.docs/kubernetes.md index fa40580f45..e553b04110 100644 --- a/.docs/kubernetes.md +++ b/.docs/kubernetes.md @@ -14,7 +14,7 @@ helm upgrade --install dbrepo \ -n dbrepo \ "oci://registry.datalab.tuwien.ac.at/dbrepo/helm/dbrepo" \ --values ./values.yaml \ - --version "1.5.2" \ + --version "1.5.3" \ --create-namespace \ --cleanup-on-fail ``` diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ac86d24826..12d2779b65 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,8 +4,8 @@ variables: TESTCONTAINERS_RYUK_DISABLED: "false" PYTHON_VERSION: "3.11" DOC_VERSION: "1.5" - APP_VERSION: "1.5.2" - CHART_VERSION: "1.5.2" + APP_VERSION: "1.5.3" + CHART_VERSION: "1.5.3" CACHE_FALLBACK_KEY: ${CI_DEFAULT_BRANCH} # This will supress any download for dependencies and plugins or upload messages which would clutter the console log. # `showDateTime` will show the passed time in milliseconds. You need to specify `--batch-mode` to make this work. diff --git a/Makefile b/Makefile index 91994bda3b..a348649ecb 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .PHONY: all -APP_VERSION ?= 1.5.2 -CHART_VERSION ?= 1.5.2 +APP_VERSION ?= 1.5.3 +CHART_VERSION ?= 1.5.3 REPOSITORY_URL ?= registry.datalab.tuwien.ac.at/dbrepo .PHONY: all diff --git a/dbrepo-analyse-service/Pipfile b/dbrepo-analyse-service/Pipfile index 150d5811a6..49d010321f 100644 --- a/dbrepo-analyse-service/Pipfile +++ b/dbrepo-analyse-service/Pipfile @@ -21,7 +21,7 @@ numpy = "*" pandas = "*" minio = "*" pydantic = "*" -dbrepo = {path = "./lib/dbrepo-1.5.2.tar.gz"} +dbrepo = {path = "./lib/dbrepo-1.5.3.tar.gz"} opensearch-py = "*" [dev-packages] diff --git a/dbrepo-analyse-service/Pipfile.lock b/dbrepo-analyse-service/Pipfile.lock index 8e7bafd426..ed07c44a60 100644 --- a/dbrepo-analyse-service/Pipfile.lock +++ b/dbrepo-analyse-service/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "3df8e885e462132896bafc8d6c82c70226fc2352f2088eac67186d3e171b8308" + "sha256": "6b47020110a5b917d5fadf008775a0cc82e88b762b0bd127895ec92419b586da" }, "pipfile-spec": 6, "requires": { @@ -26,85 +26,85 @@ }, "aiohttp": { "hashes": [ - "sha256:0411777249f25d11bd2964a230b3ffafcbed6cd65d0f2b132bc2b8f5b8c347c7", - "sha256:0a97d657f6cf8782a830bb476c13f7d777cfcab8428ac49dde15c22babceb361", - "sha256:0b5a5009b0159a8f707879dc102b139466d8ec6db05103ec1520394fdd8ea02c", - "sha256:0bcb7f6976dc0b6b56efde13294862adf68dd48854111b422a336fa729a82ea6", - "sha256:14624d96f0d69cf451deed3173079a68c322279be6030208b045ab77e1e8d550", - "sha256:15c4e489942d987d5dac0ba39e5772dcbed4cc9ae3710d1025d5ba95e4a5349c", - "sha256:176f8bb8931da0613bb0ed16326d01330066bb1e172dd97e1e02b1c27383277b", - "sha256:17af09d963fa1acd7e4c280e9354aeafd9e3d47eaa4a6bfbd2171ad7da49f0c5", - "sha256:1a8b13b9950d8b2f8f58b6e5842c4b842b5887e2c32e3f4644d6642f1659a530", - "sha256:202f40fb686e5f93908eee0c75d1e6fbe50a43e9bd4909bf3bf4a56b560ca180", - "sha256:21cbe97839b009826a61b143d3ca4964c8590d7aed33d6118125e5b71691ca46", - "sha256:27935716f8d62c1c73010428db310fd10136002cfc6d52b0ba7bdfa752d26066", - "sha256:282e0a7ddd36ebc411f156aeaa0491e8fe7f030e2a95da532cf0c84b0b70bc66", - "sha256:28f29bce89c3b401a53d6fd4bee401ee943083bf2bdc12ef297c1d63155070b0", - "sha256:2ac9fd83096df36728da8e2f4488ac3b5602238f602706606f3702f07a13a409", - "sha256:30f9f89ae625d412043f12ca3771b2ccec227cc93b93bb1f994db6e1af40a7d3", - "sha256:317251b9c9a2f1a9ff9cd093775b34c6861d1d7df9439ce3d32a88c275c995cd", - "sha256:31de2f10f63f96cc19e04bd2df9549559beadd0b2ee2da24a17e7ed877ca8c60", - "sha256:36df00e0541f264ce42d62280281541a47474dfda500bc5b7f24f70a7f87be7a", - "sha256:39625703540feb50b6b7f938b3856d1f4886d2e585d88274e62b1bd273fae09b", - "sha256:3f5461c77649358610fb9694e790956b4238ac5d9e697a17f63619c096469afe", - "sha256:4313f3bc901255b22f01663eeeae167468264fdae0d32c25fc631d5d6e15b502", - "sha256:442356e8924fe1a121f8c87866b0ecdc785757fd28924b17c20493961b3d6697", - "sha256:44cb1a1326a0264480a789e6100dc3e07122eb8cd1ad6b784a3d47d13ed1d89c", - "sha256:44d323aa80a867cb6db6bebb4bbec677c6478e38128847f2c6b0f70eae984d72", - "sha256:499368eb904566fbdf1a3836a1532000ef1308f34a1bcbf36e6351904cced771", - "sha256:4b01d9cfcb616eeb6d40f02e66bebfe7b06d9f2ef81641fdd50b8dd981166e0b", - "sha256:5720ebbc7a1b46c33a42d489d25d36c64c419f52159485e55589fbec648ea49a", - "sha256:5cc5e0d069c56645446c45a4b5010d4b33ac6c5ebfd369a791b5f097e46a3c08", - "sha256:618b18c3a2360ac940a5503da14fa4f880c5b9bc315ec20a830357bcc62e6bae", - "sha256:6435a66957cdba1a0b16f368bde03ce9c79c57306b39510da6ae5312a1a5b2c1", - "sha256:647ec5bee7e4ec9f1034ab48173b5fa970d9a991e565549b965e93331f1328fe", - "sha256:6e1e9e447856e9b7b3d38e1316ae9a8c92e7536ef48373de758ea055edfd5db5", - "sha256:6ef1550bb5f55f71b97a6a395286db07f7f2c01c8890e613556df9a51da91e8d", - "sha256:6ffa45cc55b18d4ac1396d1ddb029f139b1d3480f1594130e62bceadf2e1a838", - "sha256:77f31cebd8c27a36af6c7346055ac564946e562080ee1a838da724585c67474f", - "sha256:7a3b5b2c012d70c63d9d13c57ed1603709a4d9d7d473e4a9dfece0e4ea3d5f51", - "sha256:7a7ddf981a0b953ade1c2379052d47ccda2f58ab678fca0671c7c7ca2f67aac2", - "sha256:84de955314aa5e8d469b00b14d6d714b008087a0222b0f743e7ffac34ef56aff", - "sha256:8dcfd14c712aa9dd18049280bfb2f95700ff6a8bde645e09f17c3ed3f05a0130", - "sha256:928f92f80e2e8d6567b87d3316c1fd9860ccfe36e87a9a7f5237d4cda8baa1ba", - "sha256:9384b07cfd3045b37b05ed002d1c255db02fb96506ad65f0f9b776b762a7572e", - "sha256:96726839a42429318017e67a42cca75d4f0d5248a809b3cc2e125445edd7d50d", - "sha256:96bbec47beb131bbf4bae05d8ef99ad9e5738f12717cfbbf16648b78b0232e87", - "sha256:9bcf97b971289be69638d8b1b616f7e557e1342debc7fc86cf89d3f08960e411", - "sha256:a0cf4d814689e58f57ecd5d8c523e6538417ca2e72ff52c007c64065cef50fb2", - "sha256:a7c6147c6306f537cff59409609508a1d2eff81199f0302dd456bb9e7ea50c39", - "sha256:a9266644064779840feec0e34f10a89b3ff1d2d6b751fe90017abcad1864fa7c", - "sha256:afbe85b50ade42ddff5669947afde9e8a610e64d2c80be046d67ec4368e555fa", - "sha256:afcda759a69c6a8be3aae764ec6733155aa4a5ad9aad4f398b52ba4037942fe3", - "sha256:b2fab23003c4bb2249729a7290a76c1dda38c438300fdf97d4e42bf78b19c810", - "sha256:bd3f711f4c99da0091ced41dccdc1bcf8be0281dc314d6d9c6b6cf5df66f37a9", - "sha256:be0c7c98e38a1e3ad7a6ff64af8b6d6db34bf5a41b1478e24c3c74d9e7f8ed42", - "sha256:c1f2d7fd583fc79c240094b3e7237d88493814d4b300d013a42726c35a734bc9", - "sha256:c5bba6b83fde4ca233cfda04cbd4685ab88696b0c8eaf76f7148969eab5e248a", - "sha256:c6beeac698671baa558e82fa160be9761cf0eb25861943f4689ecf9000f8ebd0", - "sha256:c7333e7239415076d1418dbfb7fa4df48f3a5b00f8fdf854fca549080455bc14", - "sha256:c8a02f74ae419e3955af60f570d83187423e42e672a6433c5e292f1d23619269", - "sha256:c9c23e62f3545c2216100603614f9e019e41b9403c47dd85b8e7e5015bf1bde0", - "sha256:cca505829cdab58c2495ff418c96092d225a1bbd486f79017f6de915580d3c44", - "sha256:d3108f0ad5c6b6d78eec5273219a5bbd884b4aacec17883ceefaac988850ce6e", - "sha256:d4b8a1b6c7a68c73191f2ebd3bf66f7ce02f9c374e309bdb68ba886bbbf1b938", - "sha256:d6e274661c74195708fc4380a4ef64298926c5a50bb10fbae3d01627d7a075b7", - "sha256:db2914de2559809fdbcf3e48f41b17a493b58cb7988d3e211f6b63126c55fe82", - "sha256:e738aabff3586091221044b7a584865ddc4d6120346d12e28e788307cd731043", - "sha256:e7f6173302f8a329ca5d1ee592af9e628d3ade87816e9958dcf7cdae2841def7", - "sha256:e9d036a9a41fc78e8a3f10a86c2fc1098fca8fab8715ba9eb999ce4788d35df0", - "sha256:ea142255d4901b03f89cb6a94411ecec117786a76fc9ab043af8f51dd50b5313", - "sha256:ebd3e6b0c7d4954cca59d241970011f8d3327633d555051c430bd09ff49dc494", - "sha256:ec656680fc53a13f849c71afd0c84a55c536206d524cbc831cde80abbe80489e", - "sha256:ec8df0ff5a911c6d21957a9182402aad7bf060eaeffd77c9ea1c16aecab5adbf", - "sha256:ed95d66745f53e129e935ad726167d3a6cb18c5d33df3165974d54742c373868", - "sha256:ef2c9499b7bd1e24e473dc1a85de55d72fd084eea3d8bdeec7ee0720decb54fa", - "sha256:f5252ba8b43906f206048fa569debf2cd0da0316e8d5b4d25abe53307f573941", - "sha256:f737fef6e117856400afee4f17774cdea392b28ecf058833f5eca368a18cf1bf", - "sha256:fc726c3fa8f606d07bd2b500e5dc4c0fd664c59be7788a16b9e34352c50b6b6b" + "sha256:012f176945af138abc10c4a48743327a92b4ca9adc7a0e078077cdb5dbab7be0", + "sha256:02c13415b5732fb6ee7ff64583a5e6ed1c57aa68f17d2bda79c04888dfdc2769", + "sha256:03b6002e20938fc6ee0918c81d9e776bebccc84690e2b03ed132331cca065ee5", + "sha256:04814571cb72d65a6899db6099e377ed00710bf2e3eafd2985166f2918beaf59", + "sha256:0580f2e12de2138f34debcd5d88894786453a76e98febaf3e8fe5db62d01c9bf", + "sha256:06a8e2ee1cbac16fe61e51e0b0c269400e781b13bcfc33f5425912391a542985", + "sha256:076bc454a7e6fd646bc82ea7f98296be0b1219b5e3ef8a488afbdd8e81fbac50", + "sha256:0c9527819b29cd2b9f52033e7fb9ff08073df49b4799c89cb5754624ecd98299", + "sha256:0dc49f42422163efb7e6f1df2636fe3db72713f6cd94688e339dbe33fe06d61d", + "sha256:14cdb5a9570be5a04eec2ace174a48ae85833c2aadc86de68f55541f66ce42ab", + "sha256:15fccaf62a4889527539ecb86834084ecf6e9ea70588efde86e8bc775e0e7542", + "sha256:24213ba85a419103e641e55c27dc7ff03536c4873470c2478cce3311ba1eee7b", + "sha256:31d5093d3acd02b31c649d3a69bb072d539d4c7659b87caa4f6d2bcf57c2fa2b", + "sha256:3691ed7726fef54e928fe26344d930c0c8575bc968c3e239c2e1a04bd8cf7838", + "sha256:386fbe79863eb564e9f3615b959e28b222259da0c48fd1be5929ac838bc65683", + "sha256:3bbbfff4c679c64e6e23cb213f57cc2c9165c9a65d63717108a644eb5a7398df", + "sha256:3de34936eb1a647aa919655ff8d38b618e9f6b7f250cc19a57a4bf7fd2062b6d", + "sha256:40d1c7a7f750b5648642586ba7206999650208dbe5afbcc5284bcec6579c9b91", + "sha256:44224d815853962f48fe124748227773acd9686eba6dc102578defd6fc99e8d9", + "sha256:47ad15a65fb41c570cd0ad9a9ff8012489e68176e7207ec7b82a0940dddfd8be", + "sha256:482cafb7dc886bebeb6c9ba7925e03591a62ab34298ee70d3dd47ba966370d2c", + "sha256:49c7dbbc1a559ae14fc48387a115b7d4bbc84b4a2c3b9299c31696953c2a5219", + "sha256:4b2c7ac59c5698a7a8207ba72d9e9c15b0fc484a560be0788b31312c2c5504e4", + "sha256:4cca22a61b7fe45da8fc73c3443150c3608750bbe27641fc7558ec5117b27fdf", + "sha256:4cfce37f31f20800a6a6620ce2cdd6737b82e42e06e6e9bd1b36f546feb3c44f", + "sha256:502a1464ccbc800b4b1995b302efaf426e8763fadf185e933c2931df7db9a199", + "sha256:53bf2097e05c2accc166c142a2090e4c6fd86581bde3fd9b2d3f9e93dda66ac1", + "sha256:593c114a2221444f30749cc5e5f4012488f56bd14de2af44fe23e1e9894a9c60", + "sha256:5d6958671b296febe7f5f859bea581a21c1d05430d1bbdcf2b393599b1cdce77", + "sha256:5ef359ebc6949e3a34c65ce20230fae70920714367c63afd80ea0c2702902ccf", + "sha256:613e5169f8ae77b1933e42e418a95931fb4867b2991fc311430b15901ed67079", + "sha256:61b9bae80ed1f338c42f57c16918853dc51775fb5cb61da70d590de14d8b5fb4", + "sha256:6362cc6c23c08d18ddbf0e8c4d5159b5df74fea1a5278ff4f2c79aed3f4e9f46", + "sha256:65a96e3e03300b41f261bbfd40dfdbf1c301e87eab7cd61c054b1f2e7c89b9e8", + "sha256:65e55ca7debae8faaffee0ebb4b47a51b4075f01e9b641c31e554fd376595c6c", + "sha256:68386d78743e6570f054fe7949d6cb37ef2b672b4d3405ce91fafa996f7d9b4d", + "sha256:68ff6f48b51bd78ea92b31079817aff539f6c8fc80b6b8d6ca347d7c02384e33", + "sha256:6ab29b8a0beb6f8eaf1e5049252cfe74adbaafd39ba91e10f18caeb0e99ffb34", + "sha256:77ae58586930ee6b2b6f696c82cf8e78c8016ec4795c53e36718365f6959dc82", + "sha256:77c4aa15a89847b9891abf97f3d4048f3c2d667e00f8a623c89ad2dccee6771b", + "sha256:78153314f26d5abef3239b4a9af20c229c6f3ecb97d4c1c01b22c4f87669820c", + "sha256:7852bbcb4d0d2f0c4d583f40c3bc750ee033265d80598d0f9cb6f372baa6b836", + "sha256:7e97d622cb083e86f18317282084bc9fbf261801b0192c34fe4b1febd9f7ae69", + "sha256:7f3dc0e330575f5b134918976a645e79adf333c0a1439dcf6899a80776c9ab39", + "sha256:80886dac673ceaef499de2f393fc80bb4481a129e6cb29e624a12e3296cc088f", + "sha256:811f23b3351ca532af598405db1093f018edf81368e689d1b508c57dcc6b6a32", + "sha256:86a5dfcc39309470bd7b68c591d84056d195428d5d2e0b5ccadfbaf25b026ebc", + "sha256:8b3cf2dc0f0690a33f2d2b2cb15db87a65f1c609f53c37e226f84edb08d10f52", + "sha256:8cc5203b817b748adccb07f36390feb730b1bc5f56683445bfe924fc270b8816", + "sha256:909af95a72cedbefe5596f0bdf3055740f96c1a4baa0dd11fd74ca4de0b4e3f1", + "sha256:974d3a2cce5fcfa32f06b13ccc8f20c6ad9c51802bb7f829eae8a1845c4019ec", + "sha256:98283b94cc0e11c73acaf1c9698dea80c830ca476492c0fe2622bd931f34b487", + "sha256:98f5635f7b74bcd4f6f72fcd85bea2154b323a9f05226a80bc7398d0c90763b0", + "sha256:99b7920e7165be5a9e9a3a7f1b680f06f68ff0d0328ff4079e5163990d046767", + "sha256:9bca390cb247dbfaec3c664326e034ef23882c3f3bfa5fbf0b56cad0320aaca5", + "sha256:9e2e576caec5c6a6b93f41626c9c02fc87cd91538b81a3670b2e04452a63def6", + "sha256:9ef405356ba989fb57f84cac66f7b0260772836191ccefbb987f414bcd2979d9", + "sha256:a55d2ad345684e7c3dd2c20d2f9572e9e1d5446d57200ff630e6ede7612e307f", + "sha256:ab7485222db0959a87fbe8125e233b5a6f01f4400785b36e8a7878170d8c3138", + "sha256:b1fc6b45010a8d0ff9e88f9f2418c6fd408c99c211257334aff41597ebece42e", + "sha256:b78f053a7ecfc35f0451d961dacdc671f4bcbc2f58241a7c820e9d82559844cf", + "sha256:b99acd4730ad1b196bfb03ee0803e4adac371ae8efa7e1cbc820200fc5ded109", + "sha256:be2b516f56ea883a3e14dda17059716593526e10fb6303189aaf5503937db408", + "sha256:beb39a6d60a709ae3fb3516a1581777e7e8b76933bb88c8f4420d875bb0267c6", + "sha256:bf3d1a519a324af764a46da4115bdbd566b3c73fb793ffb97f9111dbc684fc4d", + "sha256:c49a76c1038c2dd116fa443eba26bbb8e6c37e924e2513574856de3b6516be99", + "sha256:c5532f0441fc09c119e1dca18fbc0687e64fbeb45aa4d6a87211ceaee50a74c4", + "sha256:c6b9e6d7e41656d78e37ce754813fa44b455c3d0d0dced2a047def7dc5570b74", + "sha256:c87bf31b7fdab94ae3adbe4a48e711bfc5f89d21cf4c197e75561def39e223bc", + "sha256:cbad88a61fa743c5d283ad501b01c153820734118b65aee2bd7dbb735475ce0d", + "sha256:cf14627232dfa8730453752e9cdc210966490992234d77ff90bc8dc0dce361d5", + "sha256:db1d0b28fcb7f1d35600150c3e4b490775251dea70f894bf15c678fdd84eda6a", + "sha256:ddf5f7d877615f6a1e75971bfa5ac88609af3b74796ff3e06879e8422729fd01", + "sha256:e44a9a3c053b90c6f09b1bb4edd880959f5328cf63052503f892c41ea786d99f", + "sha256:efb15a17a12497685304b2d976cb4939e55137df7b09fa53f1b6a023f01fcb4e", + "sha256:fbbaea811a2bba171197b08eea288b9402faa2bab2ba0858eecdd0a4105753a3" ], "markers": "python_version >= '3.9'", - "version": "==3.11.9" + "version": "==3.11.10" }, "aiosignal": { "hashes": [ @@ -175,20 +175,20 @@ }, "boto3": { "hashes": [ - "sha256:88370c6845ba71a4dae7f6b357099df29b3965da584be040c8e72c9902bc9492", - "sha256:dab5bddbbe57dc707b6f6a1f25dc2823b8e234b6fe99fafef7fc406ab73031b9" + "sha256:21a3b18c3a7fd20e463708fe3fa035983105dc7f3a1c274e1903e1583ab91159", + "sha256:50dae461ab5fbedfb81b690895d48a918fed0d5fdff37be1c4232770c0dc9712" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.35.74" + "version": "==1.35.80" }, "botocore": { "hashes": [ - "sha256:9ac9d33d84dd9f05b35085de081552342a2c9ae22e3c4ee105723c9e92c07bd9", - "sha256:de5c4fa9a24cef3a758974857b5c5820a12fad345ebf33c052a5988e88f33634" + "sha256:36e589dccb62380abd628b08fecfa2f7c89b99f41ec9fc42c467c94008c0be4a", + "sha256:b8dfceca58891cb2711bd6455ec4f7159051f3796e0f64adef9bb334f19d8a92" ], "markers": "python_version >= '3.8'", - "version": "==1.35.74" + "version": "==1.35.80" }, "certifi": { "hashes": [ @@ -397,7 +397,6 @@ "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b", "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc", "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543", - "sha256:60eb32934076fa07e4316b7b2742fa52cbb190b42c2df2863dbc4230a0a9b385", "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c", "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591", "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede", @@ -405,7 +404,6 @@ "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f", "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123", "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c", - "sha256:9abcc2e083cbe8dde89124a47e5e53ec38751f0d7dfd36801008f316a127d7ba", "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c", "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285", "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd", @@ -427,9 +425,9 @@ }, "dbrepo": { "hashes": [ - "sha256:292c2631816ca3dbdbd243e4c006c4bd39d512f5ae7e4b10070102c85ec58a10" + "sha256:b746668ca7830897aa9ab7660c2b6cf420a6cde09afb87665f9fa9cb4d6a2314" ], - "path": "./lib/dbrepo-1.5.2.tar.gz" + "path": "./lib/dbrepo-1.5.3.tar.gz" }, "events": { "hashes": [ @@ -959,65 +957,65 @@ }, "numpy": { "hashes": [ - "sha256:016d0f6f5e77b0f0d45d77387ffa4bb89816b57c835580c3ce8e099ef830befe", - "sha256:02135ade8b8a84011cbb67dc44e07c58f28575cf9ecf8ab304e51c05528c19f0", - "sha256:08788d27a5fd867a663f6fc753fd7c3ad7e92747efc73c53bca2f19f8bc06f48", - "sha256:0d30c543f02e84e92c4b1f415b7c6b5326cbe45ee7882b6b77db7195fb971e3a", - "sha256:0fa14563cc46422e99daef53d725d0c326e99e468a9320a240affffe87852564", - "sha256:13138eadd4f4da03074851a698ffa7e405f41a0845a6b1ad135b81596e4e9958", - "sha256:14e253bd43fc6b37af4921b10f6add6925878a42a0c5fe83daee390bca80bc17", - "sha256:15cb89f39fa6d0bdfb600ea24b250e5f1a3df23f901f51c8debaa6a5d122b2f0", - "sha256:17ee83a1f4fef3c94d16dc1802b998668b5419362c8a4f4e8a491de1b41cc3ee", - "sha256:2312b2aa89e1f43ecea6da6ea9a810d06aae08321609d8dc0d0eda6d946a541b", - "sha256:2564fbdf2b99b3f815f2107c1bbc93e2de8ee655a69c261363a1172a79a257d4", - "sha256:3522b0dfe983a575e6a9ab3a4a4dfe156c3e428468ff08ce582b9bb6bd1d71d4", - "sha256:4394bc0dbd074b7f9b52024832d16e019decebf86caf909d94f6b3f77a8ee3b6", - "sha256:45966d859916ad02b779706bb43b954281db43e185015df6eb3323120188f9e4", - "sha256:4d1167c53b93f1f5d8a139a742b3c6f4d429b54e74e6b57d0eff40045187b15d", - "sha256:4f2015dfe437dfebbfce7c85c7b53d81ba49e71ba7eadbf1df40c915af75979f", - "sha256:50ca6aba6e163363f132b5c101ba078b8cbd3fa92c7865fd7d4d62d9779ac29f", - "sha256:50d18c4358a0a8a53f12a8ba9d772ab2d460321e6a93d6064fc22443d189853f", - "sha256:5641516794ca9e5f8a4d17bb45446998c6554704d888f86df9b200e66bdcce56", - "sha256:576a1c1d25e9e02ed7fa5477f30a127fe56debd53b8d2c89d5578f9857d03ca9", - "sha256:6a4825252fcc430a182ac4dee5a505053d262c807f8a924603d411f6718b88fd", - "sha256:72dcc4a35a8515d83e76b58fdf8113a5c969ccd505c8a946759b24e3182d1f23", - "sha256:747641635d3d44bcb380d950679462fae44f54b131be347d5ec2bce47d3df9ed", - "sha256:762479be47a4863e261a840e8e01608d124ee1361e48b96916f38b119cfda04a", - "sha256:78574ac2d1a4a02421f25da9559850d59457bac82f2b8d7a44fe83a64f770098", - "sha256:825656d0743699c529c5943554d223c021ff0494ff1442152ce887ef4f7561a1", - "sha256:8637dcd2caa676e475503d1f8fdb327bc495554e10838019651b76d17b98e512", - "sha256:96fe52fcdb9345b7cd82ecd34547fca4321f7656d500eca497eb7ea5a926692f", - "sha256:973faafebaae4c0aaa1a1ca1ce02434554d67e628b8d805e61f874b84e136b09", - "sha256:996bb9399059c5b82f76b53ff8bb686069c05acc94656bb259b1d63d04a9506f", - "sha256:a38c19106902bb19351b83802531fea19dee18e5b37b36454f27f11ff956f7fc", - "sha256:a6b46587b14b888e95e4a24d7b13ae91fa22386c199ee7b418f449032b2fa3b8", - "sha256:a9f7f672a3388133335589cfca93ed468509cb7b93ba3105fce780d04a6576a0", - "sha256:aa08e04e08aaf974d4458def539dece0d28146d866a39da5639596f4921fd761", - "sha256:b0df3635b9c8ef48bd3be5f862cf71b0a4716fa0e702155c45067c6b711ddcef", - "sha256:b47fbb433d3260adcd51eb54f92a2ffbc90a4595f8970ee00e064c644ac788f5", - "sha256:baed7e8d7481bfe0874b566850cb0b85243e982388b7b23348c6db2ee2b2ae8e", - "sha256:bc6f24b3d1ecc1eebfbf5d6051faa49af40b03be1aaa781ebdadcbc090b4539b", - "sha256:c006b607a865b07cd981ccb218a04fc86b600411d83d6fc261357f1c0966755d", - "sha256:c181ba05ce8299c7aa3125c27b9c2167bca4a4445b7ce73d5febc411ca692e43", - "sha256:c7662f0e3673fe4e832fe07b65c50342ea27d989f92c80355658c7f888fcc83c", - "sha256:c80e4a09b3d95b4e1cac08643f1152fa71a0a821a2d4277334c88d54b2219a41", - "sha256:c894b4305373b9c5576d7a12b473702afdf48ce5369c074ba304cc5ad8730dff", - "sha256:d7aac50327da5d208db2eec22eb11e491e3fe13d22653dce51b0f4109101b408", - "sha256:d89dd2b6da69c4fff5e39c28a382199ddedc3a5be5390115608345dec660b9e2", - "sha256:d9beb777a78c331580705326d2367488d5bc473b49a9bc3036c154832520aca9", - "sha256:dc258a761a16daa791081d026f0ed4399b582712e6fc887a95af09df10c5ca57", - "sha256:e14e26956e6f1696070788252dcdff11b4aca4c3e8bd166e0df1bb8f315a67cb", - "sha256:e6988e90fcf617da2b5c78902fe8e668361b43b4fe26dbf2d7b0f8034d4cafb9", - "sha256:e711e02f49e176a01d0349d82cb5f05ba4db7d5e7e0defd026328e5cfb3226d3", - "sha256:ea4dedd6e394a9c180b33c2c872b92f7ce0f8e7ad93e9585312b0c5a04777a4a", - "sha256:ecc76a9ba2911d8d37ac01de72834d8849e55473457558e12995f4cd53e778e0", - "sha256:f55ba01150f52b1027829b50d70ef1dafd9821ea82905b63936668403c3b471e", - "sha256:f653490b33e9c3a4c1c01d41bc2aef08f9475af51146e4a7710c450cf9761598", - "sha256:fa2d1337dc61c8dc417fbccf20f6d1e139896a30721b7f1e832b2bb6ef4eb6c4" + "sha256:0557eebc699c1c34cccdd8c3778c9294e8196df27d713706895edc6f57d29608", + "sha256:0798b138c291d792f8ea40fe3768610f3c7dd2574389e37c3f26573757c8f7ef", + "sha256:0da8495970f6b101ddd0c38ace92edea30e7e12b9a926b57f5fabb1ecc25bb90", + "sha256:0f0986e917aca18f7a567b812ef7ca9391288e2acb7a4308aa9d265bd724bdae", + "sha256:122fd2fcfafdefc889c64ad99c228d5a1f9692c3a83f56c292618a59aa60ae83", + "sha256:140dd80ff8981a583a60980be1a655068f8adebf7a45a06a6858c873fcdcd4a0", + "sha256:16757cf28621e43e252c560d25b15f18a2f11da94fea344bf26c599b9cf54b73", + "sha256:18142b497d70a34b01642b9feabb70156311b326fdddd875a9981f34a369b671", + "sha256:1c92113619f7b272838b8d6702a7f8ebe5edea0df48166c47929611d0b4dea69", + "sha256:1e25507d85da11ff5066269d0bd25d06e0a0f2e908415534f3e603d2a78e4ffa", + "sha256:30bf971c12e4365153afb31fc73f441d4da157153f3400b82db32d04de1e4066", + "sha256:3579eaeb5e07f3ded59298ce22b65f877a86ba8e9fe701f5576c99bb17c283da", + "sha256:36b2b43146f646642b425dd2027730f99bac962618ec2052932157e213a040e9", + "sha256:3905a5fffcc23e597ee4d9fb3fcd209bd658c352657548db7316e810ca80458e", + "sha256:3a4199f519e57d517ebd48cb76b36c82da0360781c6a0353e64c0cac30ecaad3", + "sha256:3f2f5cddeaa4424a0a118924b988746db6ffa8565e5829b1841a8a3bd73eb59a", + "sha256:40deb10198bbaa531509aad0cd2f9fadb26c8b94070831e2208e7df543562b74", + "sha256:440cfb3db4c5029775803794f8638fbdbf71ec702caf32735f53b008e1eaece3", + "sha256:4723a50e1523e1de4fccd1b9a6dcea750c2102461e9a02b2ac55ffeae09a4410", + "sha256:4bddbaa30d78c86329b26bd6aaaea06b1e47444da99eddac7bf1e2fab717bd72", + "sha256:4e58666988605e251d42c2818c7d3d8991555381be26399303053b58a5bbf30d", + "sha256:54dc1d6d66f8d37843ed281773c7174f03bf7ad826523f73435deb88ba60d2d4", + "sha256:57fcc997ffc0bef234b8875a54d4058afa92b0b0c4223fc1f62f24b3b5e86038", + "sha256:58b92a5828bd4d9aa0952492b7de803135038de47343b2aa3cc23f3b71a3dc4e", + "sha256:5a145e956b374e72ad1dff82779177d4a3c62bc8248f41b80cb5122e68f22d13", + "sha256:6ab153263a7c5ccaf6dfe7e53447b74f77789f28ecb278c3b5d49db7ece10d6d", + "sha256:7832f9e8eb00be32f15fdfb9a981d6955ea9adc8574c521d48710171b6c55e95", + "sha256:7fe4bb0695fe986a9e4deec3b6857003b4cfe5c5e4aac0b95f6a658c14635e31", + "sha256:7fe8f3583e0607ad4e43a954e35c1748b553bfe9fdac8635c02058023277d1b3", + "sha256:85ad7d11b309bd132d74397fcf2920933c9d1dc865487128f5c03d580f2c3d03", + "sha256:9874bc2ff574c40ab7a5cbb7464bf9b045d617e36754a7bc93f933d52bd9ffc6", + "sha256:a184288538e6ad699cbe6b24859206e38ce5fba28f3bcfa51c90d0502c1582b2", + "sha256:a222d764352c773aa5ebde02dd84dba3279c81c6db2e482d62a3fa54e5ece69b", + "sha256:a50aeff71d0f97b6450d33940c7181b08be1441c6c193e678211bff11aa725e7", + "sha256:a55dc7a7f0b6198b07ec0cd445fbb98b05234e8b00c5ac4874a63372ba98d4ab", + "sha256:a62eb442011776e4036af5c8b1a00b706c5bc02dc15eb5344b0c750428c94219", + "sha256:a7d41d1612c1a82b64697e894b75db6758d4f21c3ec069d841e60ebe54b5b571", + "sha256:a98f6f20465e7618c83252c02041517bd2f7ea29be5378f09667a8f654a5918d", + "sha256:afe8fb968743d40435c3827632fd36c5fbde633b0423da7692e426529b1759b1", + "sha256:b0b227dcff8cdc3efbce66d4e50891f04d0a387cce282fe1e66199146a6a8fca", + "sha256:b30042fe92dbd79f1ba7f6898fada10bdaad1847c44f2dff9a16147e00a93661", + "sha256:b606b1aaf802e6468c2608c65ff7ece53eae1a6874b3765f69b8ceb20c5fa78e", + "sha256:b6207dc8fb3c8cb5668e885cef9ec7f70189bec4e276f0ff70d5aa078d32c88e", + "sha256:c2aed8fcf8abc3020d6a9ccb31dbc9e7d7819c56a348cc88fd44be269b37427e", + "sha256:cb24cca1968b21355cc6f3da1a20cd1cebd8a023e3c5b09b432444617949085a", + "sha256:cff210198bb4cae3f3c100444c5eaa573a823f05c253e7188e1362a5555235b3", + "sha256:d35717333b39d1b6bb8433fa758a55f1081543de527171543a2b710551d40881", + "sha256:df12a1f99b99f569a7c2ae59aa2d31724e8d835fc7f33e14f4792e3071d11221", + "sha256:e09d40edfdb4e260cb1567d8ae770ccf3b8b7e9f0d9b5c2a9992696b30ce2742", + "sha256:e12c6c1ce84628c52d6367863773f7c8c8241be554e8b79686e91a43f1733773", + "sha256:e2b8cd48a9942ed3f85b95ca4105c45758438c7ed28fff1e4ce3e57c3b589d8e", + "sha256:e500aba968a48e9019e42c0c199b7ec0696a97fa69037bea163b55398e390529", + "sha256:ebe5e59545401fbb1b24da76f006ab19734ae71e703cdb4a8b347e84a0cece67", + "sha256:f0dd071b95bbca244f4cb7f70b77d2ff3aaaba7fa16dc41f58d14854a6204e6c", + "sha256:f8c8b141ef9699ae777c6278b52c706b653bf15d135d302754f6b2e90eb30367" ], "index": "pypi", "markers": "python_version >= '3.10'", - "version": "==2.1.3" + "version": "==2.2.0" }, "opensearch-py": { "hashes": [ @@ -1459,112 +1457,112 @@ }, "rpds-py": { "hashes": [ - "sha256:0545928bdf53dfdfcab284468212efefb8a6608ca3b6910c7fb2e5ed8bdc2dc0", - "sha256:05fdeae9010533e47715c37df83264df0122584e40d691d50cf3607c060952a3", - "sha256:09a1f000c5f6e08b298275bae00921e9fbbf2a35dae0a86db2821c058c2201a9", - "sha256:0a53592cdf98cec3dfcdb24ffec8a4797e7656b65700099af43ec7df023b6de4", - "sha256:0f057a0c546c42964836b209d8de9ea1a4f4b0432006c6343cbe633d8ca14571", - "sha256:0f9eb37d3a60b262a98ab51ee899cac039de9ca0ce68dcf1a6518a09719020b0", - "sha256:102be79c4cc47a4aeb5912401185c404cd2601c15a7163bbecff7f1bfe20b669", - "sha256:128cbaed7ba26116820bcb992405d6a13ea18c8fca1b8c4f59906d858e91e979", - "sha256:149b4d875ef9b12a8f5e303e86a32a58f8ef627e57ec97a7d0e4be819069d141", - "sha256:153248f48d6f90a295a502f53ec544a3ffbd21b0bb32f5dca39c4b93a764d6a2", - "sha256:157a023bded0618a1eea54979fe2e0f9309e9ddc818ef4b8fc3b884ff38fedd5", - "sha256:15fa4ca658f8ad22645d3531682b17e5580832efbfa87304c3e62214c79c1e8a", - "sha256:198067aa6f3d942ff5d0d655bb1e91b59ae85279d47590682cba2834ac1b97d2", - "sha256:1c40e02cc4f3e18fd39344edb10eebe04bd11cfd13119606b5771e5ea51630d3", - "sha256:1ded65691a1d3fd7d2aa89d2c91aa51f941601bb2ce099739909034d957fef4b", - "sha256:201650b309c419143775c15209c620627de3c09a27c7fb58375325aec5cce260", - "sha256:2143c3aed85992604d758bbe67da839fb4aab3dd2e1c6dddab5b3ca7162b34a2", - "sha256:2177e59c033bf0d1bf7de1ced561205963583caf3242c6c700a723034bfb5f8e", - "sha256:2ea23f1525d4f64286dbe0947c929d45c3ffe963b2dbed1d3844a2e4938bda42", - "sha256:31264187fc934ff1024a4f56775f33c9252d3f4f3e27ec07d1995a26b52702c3", - "sha256:36ce951800ed2acc6772fd9f42150f29d567f0423989748052fdb39d9e2b5795", - "sha256:3aaa22487477de9618ce3b37f99fbe81219ba96f3c2ca84f576f0ab451b83aba", - "sha256:3e7e99e2af59c56c59b6c964d612511b8203480d39d1ef83edc56f2cb42a3f5d", - "sha256:413a30a99d8683dace3765885920ed27ab662efbb6c98d81db76c397ad1ffd71", - "sha256:447ae1104fb32197b9262f772d565d38e834cc2e9edd89350b37b88fed636e70", - "sha256:4659b2e4a5008715099e216050f5c6976e5a4329482664411789968b82e3f17d", - "sha256:48ee97c7c6027fd423058675b5a39d0b5f7a1648250b671563d5c9f74ff13ff0", - "sha256:4ba6c66fbc6015b2f99e7176fec41793cecb00c4cc357cad038dff85e6ac42ab", - "sha256:4c8dc7331e8cbb1c0ea2bcb550adb1777365944ffd125c69aa1117fdef4887f5", - "sha256:50e4b5d291105f7063259fe0125b1af902fb34499444d7c5c521dd8328b00939", - "sha256:542eb246d5be31b5e0a9c8ddb9539416f9b31f58f75bd4ee328bff2b5c58d6fd", - "sha256:55d371b9d8b0c2a68a50413a8cb01c3c3ce1ea4f768bf77b66669a9a486e101e", - "sha256:580ccbf11f02f948add4cb641843030a89f1463d7c0740cbfc9aca91e9dc34b3", - "sha256:5dbff9402c2bdf00bf0df9905694b3c292a3847c725651938a72f554351a5fcb", - "sha256:5f941fb86195f97be7f6efe04a21b223f05dfe4d1dfb159999e2f8d101e44cc4", - "sha256:608c84699b2db09c6a8743845b1a3dad36fae53eaaecb241d45b13dff74405fb", - "sha256:626b9feb01bff049a5aec4804f0c58db12585778b4902e5376a95b01f80a7a16", - "sha256:66f4f48a89cdd30ab3a47335df81c76e9a63799d0d84b29c0618371c66fa37b0", - "sha256:6c8e97e19aa7b0b0d801a159f932ce4435f1049c8c38e2bb372bb5bee559ce50", - "sha256:72407065ad459db9f3d052ea8c51e02534f02533fc61e51cbab3bd94166f086c", - "sha256:734783dd7da58f76222f458346ddebdb3621686a1a2a667db5049caf0c9956b9", - "sha256:76eaa4c087a061a2c8a0a92536405069878a8f530c00e84a9eaf332e70f5561f", - "sha256:776a06cb5720556a549829896a49acebb5bdd96c7bba100191a994053546975a", - "sha256:7839b7528faa4d134c183b1f2dd1ee4dc2ca2f899f4f0cfdf00fc04c255262a7", - "sha256:8080467df22feca0fc9c46567001777c6fbc2b4a2683a7137420896051874ca1", - "sha256:85060e96953647871957d41707adb8d7bff4e977042fd0deb4fc1881b98dd2fe", - "sha256:8954b9ffe60f479a0c0ba40987db2546c735ab02a725ea7fd89342152d4d821d", - "sha256:8a603155db408f773637f9e3a712c6e3cbc521aaa8fa2b99f9ba6106c59a2496", - "sha256:8bd9ec1db79a664f4cbb12878693b73416f4d2cb425d3e27eccc1bdfbdc826ef", - "sha256:8c0c324879d483504b07f7b18eb1b50567c434263bbe4866ecce33056162668a", - "sha256:8ce729f1dc8a4a190c34b69f75377bddc004079b2963ab722ab91fafe040be6d", - "sha256:8ec41049c90d204a6561238a9ad6c7263ebb7009d9759c98b58078d9d2fec9ba", - "sha256:959ae04ed30cde606f3a0320f0a1f4167a107e685ef5209cce28c5080590bd31", - "sha256:96559e05bdf938b2048353e10a7920b98f853cefe4482c2064a718d7d0a50bd7", - "sha256:96b3759d8ab2323324e0a92b2f44834f9d88089b8d1ab6f533b61f4be3411cef", - "sha256:97c5ffe47ccf92d8b17e10f8a5ce28d015aa1196edc3359684cf31504eae6a14", - "sha256:9d5b925156a746dc1f5f52376fdd1fbdd3f6ffe1fcd6f5e06f77ca79abb940a3", - "sha256:9dae4eb9b5534e09ba6c6ab496a757e5e394b7e7b08767d25ca37e8d36491114", - "sha256:a083221b6a4ecdef38a60c95d8d3223d99449cb4da2544e9644958dc16664eb9", - "sha256:a0ed14a4162c2c2b21a162c9fcf90057e3e7da18cd171ab344c1e1664f75090e", - "sha256:a18aedc032d6468b73ebbe4437129cb30d54fe543cde2f23671ecad76c3aea24", - "sha256:a451dba533be77454ebcffc85189108fc05f279100835ac76e7989edacb89156", - "sha256:aa2ba0176037c915d8660a4e46581d645e2c22b5373e466bc8640a794d45861a", - "sha256:ab27dd4edd84b13309f268ffcdfc07aef8339135ffab7b6d43f16884307a2a48", - "sha256:ab784621d3e2a41916e21f13a483602cc989fd45fff637634b9231ba43d4383b", - "sha256:b07fa9e634234e84096adfa4be3828c8f26e238679c122824b2b3d7131bec578", - "sha256:b09209cdfcacf5eba9cf80367130532e6c02e695252e1f64d3cfcc2356e6e19f", - "sha256:babec324e8654a59122aaa66936a9a483faa03276db9792f51332475c2dddc4a", - "sha256:bca4428c4a957b78ded3e6e62884ab03f029dce8fa8d34818da0f80f61332b49", - "sha256:c0467838c90435b80793cde486a318fc916ee57f2af54e4b10c72b20cbdcbaa9", - "sha256:c2a214bf5b79bd39a9de1c991353aaaacafda83ba1374178309e92be8e67d411", - "sha256:c3029f481b31f329b1fdb4ec4b56935d82210ddd9c6f86ea5a87c06f1e97b161", - "sha256:c6f3fd617db422c9d4e12cb8d84c984fe07d6d9cb0950cbf117f3bccc6268d05", - "sha256:c783e4ed68200f4e03c125690d23158b1c49c4b186d458a18debc109bbdc3c2e", - "sha256:c8502a02ae3ae67084f5a0bf5a8253b19fa7a887f824e41e016cdb0ac532a06f", - "sha256:c88535f83f7391cf3a45af990237e3939a6fdfbedaed2571633bfdd0bceb36b0", - "sha256:c9ce6b83597d45bec44a2690857ede62fc98223772135f8a7fa90884eb726501", - "sha256:ca4657e9fd0b1b5376942d403d634ce188f79064f0873aa853ab05b10185ceec", - "sha256:d0ff8d5b13ce2357fa8b33a0a2e3775aa71df5bf7c8ba060634c9d15ab12f357", - "sha256:d280b4bf09f719b89fd9aab3b71067acc0d0449b7d1eba99a2ade4939cef8296", - "sha256:d3777c446bb1c5fcd82dc3f8776e1a146cd91e80cc1892f8634575ace438d22f", - "sha256:d7833ef6f5d6cb634f296abfd93452fb3eb44c4e9a6ae95c1021eab704c1cee2", - "sha256:d8306f27418361b788e3fca9f47dec125457f80122e7e31ba7ff5cdba98343f8", - "sha256:d962e2e89b3a95e3597a34b8c93ced1e98958502c5b8096c9fd69deff279f561", - "sha256:dbe428d0ac6eacaf05402adbaf137f59ad6063848182d1ff294f95ce0f24005b", - "sha256:e4f91d702b9ce1388660b3d4a28aa552614a1399e93f718ed0dacd68f23b3d32", - "sha256:e69acdbc132c9592c8dc393af85e38e206ca847c7019a953ff625191c3a12312", - "sha256:e8056adcefa2dcb67e8bc91ea5eee26df66e8b297a8cd6ff0903f85c70908fa0", - "sha256:e9ac7280bd045f472b50306d7efeee051b69e3a2dd1b90f46bd7e86e63b1efa2", - "sha256:eb013aa01b404219f28dc973d9e6310fd4db216d7299253dd355629952e0564e", - "sha256:ec1ccc2a9f764cd632fb8ab28fdde166250df54fc8d97315a4a6948dc5367639", - "sha256:ef7282d8a14b60dd515e47060638687710b1d518f4b5e961caad43fb3a3606f9", - "sha256:ef92b1fbe6aa2e7885eb90853cc016b1fc95439a8cc8da6d526880e9e2148695", - "sha256:efb2ad60ca8637d5f9f653f9a9a8d73964059972b6b95036be77e028bffc68a3", - "sha256:effcae2152afe7937a28376dbabb25c770ef99ed4e16a4ffeb8e6a4f7c4f06aa", - "sha256:f2d1b58a0c3a73f0361759642e80260a6d28eee6501b40fe25b82af33ef83f21", - "sha256:f57e2d0f8022783426121b586d7c842ea40ea832a29e28ca36c881b54c74fb28", - "sha256:f5cae9b415ea8a6a563566dbf46650222eccc5971c7daa16fbee63aef92ae543", - "sha256:f76c6f319e57007ad52e671ec741d801324760a377e3d4992c9bb8200333ebac", - "sha256:f91bfc39f7a64168e08ab831fa497ec5438c1d6c6e2f9e12848d95ad11ac8523", - "sha256:fdaee3947eaaa52dae3ceb9d9f66329e13d8bae35682b1e5dd54612938693934", - "sha256:fe3f245c2f39a5692d9123c174bc48f6f9fe3e96407e67c6d04541a767d99e72", - "sha256:ffae97d28ea4f2c613a751d087b75a97fb78311b38cc2e9a2f4587e473ace167" + "sha256:009de23c9c9ee54bf11303a966edf4d9087cd43a6003672e6aa7def643d06518", + "sha256:02fbb9c288ae08bcb34fb41d516d5eeb0455ac35b5512d03181d755d80810059", + "sha256:0a0461200769ab3b9ab7e513f6013b7a97fdeee41c29b9db343f3c5a8e2b9e61", + "sha256:0b09865a9abc0ddff4e50b5ef65467cd94176bf1e0004184eb915cbc10fc05c5", + "sha256:0b8db6b5b2d4491ad5b6bdc2bc7c017eec108acbf4e6785f42a9eb0ba234f4c9", + "sha256:0c150c7a61ed4a4f4955a96626574e9baf1adf772c2fb61ef6a5027e52803543", + "sha256:0f3cec041684de9a4684b1572fe28c7267410e02450f4561700ca5a3bc6695a2", + "sha256:1352ae4f7c717ae8cba93421a63373e582d19d55d2ee2cbb184344c82d2ae55a", + "sha256:177c7c0fce2855833819c98e43c262007f42ce86651ffbb84f37883308cb0e7d", + "sha256:1978d0021e943aae58b9b0b196fb4895a25cc53d3956b8e35e0b7682eefb6d56", + "sha256:1a60bce91f81ddaac922a40bbb571a12c1070cb20ebd6d49c48e0b101d87300d", + "sha256:1aef18820ef3e4587ebe8b3bc9ba6e55892a6d7b93bac6d29d9f631a3b4befbd", + "sha256:1e9663daaf7a63ceccbbb8e3808fe90415b0757e2abddbfc2e06c857bf8c5e2b", + "sha256:20070c65396f7373f5df4005862fa162db5d25d56150bddd0b3e8214e8ef45b4", + "sha256:214b7a953d73b5e87f0ebece4a32a5bd83c60a3ecc9d4ec8f1dca968a2d91e99", + "sha256:22bebe05a9ffc70ebfa127efbc429bc26ec9e9b4ee4d15a740033efda515cf3d", + "sha256:24e8abb5878e250f2eb0d7859a8e561846f98910326d06c0d51381fed59357bd", + "sha256:26fd7cac7dd51011a245f29a2cc6489c4608b5a8ce8d75661bb4a1066c52dfbe", + "sha256:27b1d3b3915a99208fee9ab092b8184c420f2905b7d7feb4aeb5e4a9c509b8a1", + "sha256:27e98004595899949bd7a7b34e91fa7c44d7a97c40fcaf1d874168bb652ec67e", + "sha256:2b8f60e1b739a74bab7e01fcbe3dddd4657ec685caa04681df9d562ef15b625f", + "sha256:2de29005e11637e7a2361fa151f780ff8eb2543a0da1413bb951e9f14b699ef3", + "sha256:2e8b55d8517a2fda8d95cb45d62a5a8bbf9dd0ad39c5b25c8833efea07b880ca", + "sha256:2fa4331c200c2521512595253f5bb70858b90f750d39b8cbfd67465f8d1b596d", + "sha256:3445e07bf2e8ecfeef6ef67ac83de670358abf2996916039b16a218e3d95e97e", + "sha256:3453e8d41fe5f17d1f8e9c383a7473cd46a63661628ec58e07777c2fff7196dc", + "sha256:378753b4a4de2a7b34063d6f95ae81bfa7b15f2c1a04a9518e8644e81807ebea", + "sha256:3af6e48651c4e0d2d166dc1b033b7042ea3f871504b6805ba5f4fe31581d8d38", + "sha256:3dfcbc95bd7992b16f3f7ba05af8a64ca694331bd24f9157b49dadeeb287493b", + "sha256:3f21f0495edea7fdbaaa87e633a8689cd285f8f4af5c869f27bc8074638ad69c", + "sha256:4041711832360a9b75cfb11b25a6a97c8fb49c07b8bd43d0d02b45d0b499a4ff", + "sha256:44d61b4b7d0c2c9ac019c314e52d7cbda0ae31078aabd0f22e583af3e0d79723", + "sha256:4617e1915a539a0d9a9567795023de41a87106522ff83fbfaf1f6baf8e85437e", + "sha256:4b232061ca880db21fa14defe219840ad9b74b6158adb52ddf0e87bead9e8493", + "sha256:5246b14ca64a8675e0a7161f7af68fe3e910e6b90542b4bfb5439ba752191df6", + "sha256:5725dd9cc02068996d4438d397e255dcb1df776b7ceea3b9cb972bdb11260a83", + "sha256:583f6a1993ca3369e0f80ba99d796d8e6b1a3a2a442dd4e1a79e652116413091", + "sha256:59259dc58e57b10e7e18ce02c311804c10c5a793e6568f8af4dead03264584d1", + "sha256:593eba61ba0c3baae5bc9be2f5232430453fb4432048de28399ca7376de9c627", + "sha256:59f4a79c19232a5774aee369a0c296712ad0e77f24e62cad53160312b1c1eaa1", + "sha256:5f0e260eaf54380380ac3808aa4ebe2d8ca28b9087cf411649f96bad6900c728", + "sha256:62d9cfcf4948683a18a9aff0ab7e1474d407b7bab2ca03116109f8464698ab16", + "sha256:64607d4cbf1b7e3c3c8a14948b99345eda0e161b852e122c6bb71aab6d1d798c", + "sha256:655ca44a831ecb238d124e0402d98f6212ac527a0ba6c55ca26f616604e60a45", + "sha256:666ecce376999bf619756a24ce15bb14c5bfaf04bf00abc7e663ce17c3f34fe7", + "sha256:68049202f67380ff9aa52f12e92b1c30115f32e6895cd7198fa2a7961621fc5a", + "sha256:69803198097467ee7282750acb507fba35ca22cc3b85f16cf45fb01cb9097730", + "sha256:6c7b99ca52c2c1752b544e310101b98a659b720b21db00e65edca34483259967", + "sha256:6dd9412824c4ce1aca56c47b0991e65bebb7ac3f4edccfd3f156150c96a7bf25", + "sha256:70eb60b3ae9245ddea20f8a4190bd79c705a22f8028aaf8bbdebe4716c3fab24", + "sha256:70fb28128acbfd264eda9bf47015537ba3fe86e40d046eb2963d75024be4d055", + "sha256:7b2513ba235829860b13faa931f3b6846548021846ac808455301c23a101689d", + "sha256:7ef9d9da710be50ff6809fed8f1963fecdfecc8b86656cadfca3bc24289414b0", + "sha256:81e69b0a0e2537f26d73b4e43ad7bc8c8efb39621639b4434b76a3de50c6966e", + "sha256:8633e471c6207a039eff6aa116e35f69f3156b3989ea3e2d755f7bc41754a4a7", + "sha256:8bd7c8cfc0b8247c8799080fbff54e0b9619e17cdfeb0478ba7295d43f635d7c", + "sha256:9253fc214112405f0afa7db88739294295f0e08466987f1d70e29930262b4c8f", + "sha256:99b37292234e61325e7a5bb9689e55e48c3f5f603af88b1642666277a81f1fbd", + "sha256:9bd7228827ec7bb817089e2eb301d907c0d9827a9e558f22f762bb690b131652", + "sha256:9beeb01d8c190d7581a4d59522cd3d4b6887040dcfc744af99aa59fef3e041a8", + "sha256:a63cbdd98acef6570c62b92a1e43266f9e8b21e699c363c0fef13bd530799c11", + "sha256:a76e42402542b1fae59798fab64432b2d015ab9d0c8c47ba7addddbaf7952333", + "sha256:ac0a03221cdb5058ce0167ecc92a8c89e8d0decdc9e99a2ec23380793c4dcb96", + "sha256:b0b4136a252cadfa1adb705bb81524eee47d9f6aab4f2ee4fa1e9d3cd4581f64", + "sha256:b25bc607423935079e05619d7de556c91fb6adeae9d5f80868dde3468657994b", + "sha256:b3d504047aba448d70cf6fa22e06cb09f7cbd761939fdd47604f5e007675c24e", + "sha256:bb47271f60660803ad11f4c61b42242b8c1312a31c98c578f79ef9387bbde21c", + "sha256:bbb232860e3d03d544bc03ac57855cd82ddf19c7a07651a7c0fdb95e9efea8b9", + "sha256:bc27863442d388870c1809a87507727b799c8460573cfbb6dc0eeaef5a11b5ec", + "sha256:bc51abd01f08117283c5ebf64844a35144a0843ff7b2983e0648e4d3d9f10dbb", + "sha256:be2eb3f2495ba669d2a985f9b426c1797b7d48d6963899276d22f23e33d47e37", + "sha256:bf9db5488121b596dbfc6718c76092fda77b703c1f7533a226a5a9f65248f8ad", + "sha256:c58e2339def52ef6b71b8f36d13c3688ea23fa093353f3a4fee2556e62086ec9", + "sha256:cfbc454a2880389dbb9b5b398e50d439e2e58669160f27b60e5eca11f68ae17c", + "sha256:cff63a0272fcd259dcc3be1657b07c929c466b067ceb1c20060e8d10af56f5bf", + "sha256:d115bffdd417c6d806ea9069237a4ae02f513b778e3789a359bc5856e0404cc4", + "sha256:d20cfb4e099748ea39e6f7b16c91ab057989712d31761d3300d43134e26e165f", + "sha256:d48424e39c2611ee1b84ad0f44fb3b2b53d473e65de061e3f460fc0be5f1939d", + "sha256:e0fa2d4ec53dc51cf7d3bb22e0aa0143966119f42a0c3e4998293a3dd2856b09", + "sha256:e32fee8ab45d3c2db6da19a5323bc3362237c8b653c70194414b892fd06a080d", + "sha256:e35ba67d65d49080e8e5a1dd40101fccdd9798adb9b050ff670b7d74fa41c566", + "sha256:e3fb866d9932a3d7d0c82da76d816996d1667c44891bd861a0f97ba27e84fc74", + "sha256:e61b02c3f7a1e0b75e20c3978f7135fd13cb6cf551bf4a6d29b999a88830a338", + "sha256:e67ba3c290821343c192f7eae1d8fd5999ca2dc99994114643e2f2d3e6138b15", + "sha256:e79dd39f1e8c3504be0607e5fc6e86bb60fe3584bec8b782578c3b0fde8d932c", + "sha256:e89391e6d60251560f0a8f4bd32137b077a80d9b7dbe6d5cab1cd80d2746f648", + "sha256:ea7433ce7e4bfc3a85654aeb6747babe3f66eaf9a1d0c1e7a4435bbdf27fea84", + "sha256:eaf16ae9ae519a0e237a0f528fd9f0197b9bb70f40263ee57ae53c2b8d48aeb3", + "sha256:eb0c341fa71df5a4595f9501df4ac5abfb5a09580081dffbd1ddd4654e6e9123", + "sha256:f276b245347e6e36526cbd4a266a417796fc531ddf391e43574cf6466c492520", + "sha256:f47ad3d5f3258bd7058d2d506852217865afefe6153a36eb4b6928758041d831", + "sha256:f56a6b404f74ab372da986d240e2e002769a7d7102cc73eb238a4f72eec5284e", + "sha256:f5cf2a0c2bdadf3791b5c205d55a37a54025c6e18a71c71f82bb536cf9a454bf", + "sha256:f5d36399a1b96e1a5fdc91e0522544580dbebeb1f77f27b2b0ab25559e103b8b", + "sha256:f60bd8423be1d9d833f230fdbccf8f57af322d96bcad6599e5a771b151398eb2", + "sha256:f612463ac081803f243ff13cccc648578e2279295048f2a8d5eb430af2bae6e3", + "sha256:f73d3fef726b3243a811121de45193c0ca75f6407fe66f3f4e183c983573e130", + "sha256:f82a116a1d03628a8ace4859556fb39fd1424c933341a08ea3ed6de1edb0283b", + "sha256:fb0ba113b4983beac1a2eb16faffd76cb41e176bf58c4afe3e14b9c681f702de", + "sha256:fb4f868f712b2dd4bcc538b0a0c1f63a2b1d584c925e69a224d759e7070a12d5", + "sha256:fb6116dfb8d1925cbdb52595560584db42a7f664617a1f7d7f6e32f138cdf37d", + "sha256:fda7cb070f442bf80b642cd56483b5548e43d366fe3f39b98e67cce780cded00", + "sha256:feea821ee2a9273771bae61194004ee2fc33f8ec7db08117ef9147d4bbcbca8e" ], "markers": "python_version >= '3.9'", - "version": "==0.22.1" + "version": "==0.22.3" }, "s3transfer": { "hashes": [ @@ -1584,11 +1582,11 @@ }, "six": { "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", + "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", - "version": "==1.16.0" + "version": "==1.17.0" }, "tinydb": { "hashes": [ @@ -1627,7 +1625,7 @@ "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9" ], - "markers": "python_version >= '3.10'", + "markers": "python_version >= '3.8'", "version": "==2.2.3" }, "werkzeug": { @@ -2008,72 +2006,72 @@ }, "coverage": { "hashes": [ - "sha256:093896e530c38c8e9c996901858ac63f3d4171268db2c9c8b373a228f459bbc5", - "sha256:09b9f848b28081e7b975a3626e9081574a7b9196cde26604540582da60235fdf", - "sha256:0b0c69f4f724c64dfbfe79f5dfb503b42fe6127b8d479b2677f2b227478db2eb", - "sha256:13618bed0c38acc418896005732e565b317aa9e98d855a0e9f211a7ffc2d6638", - "sha256:13690e923a3932e4fad4c0ebfb9cb5988e03d9dcb4c5150b5fcbf58fd8bddfc4", - "sha256:177f01eeaa3aee4a5ffb0d1439c5952b53d5010f86e9d2667963e632e30082cc", - "sha256:193e3bffca48ad74b8c764fb4492dd875038a2f9925530cb094db92bb5e47bed", - "sha256:1defe91d41ce1bd44b40fabf071e6a01a5aa14de4a31b986aa9dfd1b3e3e414a", - "sha256:1f188a2402f8359cf0c4b1fe89eea40dc13b52e7b4fd4812450da9fcd210181d", - "sha256:202a2d645c5a46b84992f55b0a3affe4f0ba6b4c611abec32ee88358db4bb649", - "sha256:24eda3a24a38157eee639ca9afe45eefa8d2420d49468819ac5f88b10de84f4c", - "sha256:2e4e0f60cb4bd7396108823548e82fdab72d4d8a65e58e2c19bbbc2f1e2bfa4b", - "sha256:379c111d3558272a2cae3d8e57e6b6e6f4fe652905692d54bad5ea0ca37c5ad4", - "sha256:37cda8712145917105e07aab96388ae76e787270ec04bcb9d5cc786d7cbb8443", - "sha256:38c51297b35b3ed91670e1e4efb702b790002e3245a28c76e627478aa3c10d83", - "sha256:3985b9be361d8fb6b2d1adc9924d01dec575a1d7453a14cccd73225cb79243ee", - "sha256:3988665ee376abce49613701336544041f2117de7b7fbfe91b93d8ff8b151c8e", - "sha256:3ac47fa29d8d41059ea3df65bd3ade92f97ee4910ed638e87075b8e8ce69599e", - "sha256:3b4b4299dd0d2c67caaaf286d58aef5e75b125b95615dda4542561a5a566a1e3", - "sha256:3ea8bb1ab9558374c0ab591783808511d135a833c3ca64a18ec927f20c4030f0", - "sha256:3fe47da3e4fda5f1abb5709c156eca207eacf8007304ce3019eb001e7a7204cb", - "sha256:428ac484592f780e8cd7b6b14eb568f7c85460c92e2a37cb0c0e5186e1a0d076", - "sha256:44e6c85bbdc809383b509d732b06419fb4544dca29ebe18480379633623baafb", - "sha256:4674f0daa1823c295845b6a740d98a840d7a1c11df00d1fd62614545c1583787", - "sha256:4be32da0c3827ac9132bb488d331cb32e8d9638dd41a0557c5569d57cf22c9c1", - "sha256:4db3ed6a907b555e57cc2e6f14dc3a4c2458cdad8919e40b5357ab9b6db6c43e", - "sha256:5c52a036535d12590c32c49209e79cabaad9f9ad8aa4cbd875b68c4d67a9cbce", - "sha256:629a1ba2115dce8bf75a5cce9f2486ae483cb89c0145795603d6554bdc83e801", - "sha256:62a66ff235e4c2e37ed3b6104d8b478d767ff73838d1222132a7a026aa548764", - "sha256:63068a11171e4276f6ece913bde059e77c713b48c3a848814a6537f35afb8365", - "sha256:63c19702db10ad79151a059d2d6336fe0c470f2e18d0d4d1a57f7f9713875dcf", - "sha256:644ec81edec0f4ad17d51c838a7d01e42811054543b76d4ba2c5d6af741ce2a6", - "sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71", - "sha256:6f4548c5ead23ad13fb7a2c8ea541357474ec13c2b736feb02e19a3085fac002", - "sha256:716a78a342679cd1177bc8c2fe957e0ab91405bd43a17094324845200b2fddf4", - "sha256:74610105ebd6f33d7c10f8907afed696e79c59e3043c5f20eaa3a46fddf33b4c", - "sha256:768939f7c4353c0fac2f7c37897e10b1414b571fd85dd9fc49e6a87e37a2e0d8", - "sha256:86cffe9c6dfcfe22e28027069725c7f57f4b868a3f86e81d1c62462764dc46d4", - "sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146", - "sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc", - "sha256:912e95017ff51dc3d7b6e2be158dedc889d9a5cc3382445589ce554f1a34c0ea", - "sha256:9a7b8ac36fd688c8361cbc7bf1cb5866977ece6e0b17c34aa0df58bda4fa18a4", - "sha256:9e89d5c8509fbd6c03d0dd1972925b22f50db0792ce06324ba069f10787429ad", - "sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28", - "sha256:b07c25d52b1c16ce5de088046cd2432b30f9ad5e224ff17c8f496d9cb7d1d451", - "sha256:b39e6011cd06822eb964d038d5dff5da5d98652b81f5ecd439277b32361a3a50", - "sha256:bd55f8fc8fa494958772a2a7302b0354ab16e0b9272b3c3d83cdb5bec5bd1779", - "sha256:c15b32a7aca8038ed7644f854bf17b663bc38e1671b5d6f43f9a2b2bd0c46f63", - "sha256:c1b4474beee02ede1eef86c25ad4600a424fe36cff01a6103cb4533c6bf0169e", - "sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc", - "sha256:c9ebfb2507751f7196995142f057d1324afdab56db1d9743aab7f50289abd022", - "sha256:d7ad66e8e50225ebf4236368cc43c37f59d5e6728f15f6e258c8639fa0dd8e6d", - "sha256:d82ab6816c3277dc962cfcdc85b1efa0e5f50fb2c449432deaf2398a2928ab94", - "sha256:d9fd2547e6decdbf985d579cf3fc78e4c1d662b9b0ff7cc7862baaab71c9cc5b", - "sha256:de38add67a0af869b0d79c525d3e4588ac1ffa92f39116dbe0ed9753f26eba7d", - "sha256:e19122296822deafce89a0c5e8685704c067ae65d45e79718c92df7b3ec3d331", - "sha256:e44961e36cb13c495806d4cac67640ac2866cb99044e210895b506c26ee63d3a", - "sha256:e4c81ed2820b9023a9a90717020315e63b17b18c274a332e3b6437d7ff70abe0", - "sha256:e683e6ecc587643f8cde8f5da6768e9d165cd31edf39ee90ed7034f9ca0eefee", - "sha256:f39e2f3530ed1626c66e7493be7a8423b023ca852aacdc91fb30162c350d2a92", - "sha256:f56f49b2553d7dd85fd86e029515a221e5c1f8cb3d9c38b470bc38bde7b8445a", - "sha256:fb9fc32399dca861584d96eccd6c980b69bbcd7c228d06fb74fe53e007aa8ef9" + "sha256:0824a28ec542a0be22f60c6ac36d679e0e262e5353203bea81d44ee81fe9c6d4", + "sha256:085161be5f3b30fd9b3e7b9a8c301f935c8313dcf928a07b116324abea2c1c2c", + "sha256:0ae1387db4aecb1f485fb70a6c0148c6cdaebb6038f1d40089b1fc84a5db556f", + "sha256:0d59fd927b1f04de57a2ba0137166d31c1a6dd9e764ad4af552912d70428c92b", + "sha256:0f957943bc718b87144ecaee70762bc2bc3f1a7a53c7b861103546d3a403f0a6", + "sha256:13a9e2d3ee855db3dd6ea1ba5203316a1b1fd8eaeffc37c5b54987e61e4194ae", + "sha256:1a330812d9cc7ac2182586f6d41b4d0fadf9be9049f350e0efb275c8ee8eb692", + "sha256:22be16571504c9ccea919fcedb459d5ab20d41172056206eb2994e2ff06118a4", + "sha256:2d10e07aa2b91835d6abec555ec8b2733347956991901eea6ffac295f83a30e4", + "sha256:35371f8438028fdccfaf3570b31d98e8d9eda8bb1d6ab9473f5a390969e98717", + "sha256:3c026eb44f744acaa2bda7493dad903aa5bf5fc4f2554293a798d5606710055d", + "sha256:41ff7b0da5af71a51b53f501a3bac65fb0ec311ebed1632e58fc6107f03b9198", + "sha256:4401ae5fc52ad8d26d2a5d8a7428b0f0c72431683f8e63e42e70606374c311a1", + "sha256:44349150f6811b44b25574839b39ae35291f6496eb795b7366fef3bd3cf112d3", + "sha256:447af20e25fdbe16f26e84eb714ba21d98868705cb138252d28bc400381f6ffb", + "sha256:4a8d8977b0c6ef5aeadcb644da9e69ae0dcfe66ec7f368c89c72e058bd71164d", + "sha256:4e12ae8cc979cf83d258acb5e1f1cf2f3f83524d1564a49d20b8bec14b637f08", + "sha256:592ac539812e9b46046620341498caf09ca21023c41c893e1eb9dbda00a70cbf", + "sha256:5e6b86b5847a016d0fbd31ffe1001b63355ed309651851295315031ea7eb5a9b", + "sha256:608a7fd78c67bee8936378299a6cb9f5149bb80238c7a566fc3e6717a4e68710", + "sha256:61f70dc68bd36810972e55bbbe83674ea073dd1dcc121040a08cdf3416c5349c", + "sha256:65dad5a248823a4996724a88eb51d4b31587aa7aa428562dbe459c684e5787ae", + "sha256:777abfab476cf83b5177b84d7486497e034eb9eaea0d746ce0c1268c71652077", + "sha256:7e216d8044a356fc0337c7a2a0536d6de07888d7bcda76febcb8adc50bdbbd00", + "sha256:85d9636f72e8991a1706b2b55b06c27545448baf9f6dbf51c4004609aacd7dcb", + "sha256:899b8cd4781c400454f2f64f7776a5d87bbd7b3e7f7bda0cb18f857bb1334664", + "sha256:8a289d23d4c46f1a82d5db4abeb40b9b5be91731ee19a379d15790e53031c014", + "sha256:8d2dfa71665a29b153a9681edb1c8d9c1ea50dfc2375fb4dac99ea7e21a0bcd9", + "sha256:8e3c3e38930cfb729cb8137d7f055e5a473ddaf1217966aa6238c88bd9fd50e6", + "sha256:8f8770dfc6e2c6a2d4569f411015c8d751c980d17a14b0530da2d7f27ffdd88e", + "sha256:932fc826442132dde42ee52cf66d941f581c685a6313feebed358411238f60f9", + "sha256:96d636c77af18b5cb664ddf12dab9b15a0cfe9c0bde715da38698c8cea748bfa", + "sha256:97ddc94d46088304772d21b060041c97fc16bdda13c6c7f9d8fcd8d5ae0d8611", + "sha256:98caba4476a6c8d59ec1eb00c7dd862ba9beca34085642d46ed503cc2d440d4b", + "sha256:9901d36492009a0a9b94b20e52ebfc8453bf49bb2b27bca2c9706f8b4f5a554a", + "sha256:99e266ae0b5d15f1ca8d278a668df6f51cc4b854513daab5cae695ed7b721cf8", + "sha256:9c38bf15a40ccf5619fa2fe8f26106c7e8e080d7760aeccb3722664c8656b030", + "sha256:a27801adef24cc30871da98a105f77995e13a25a505a0161911f6aafbd66e678", + "sha256:abd3e72dd5b97e3af4246cdada7738ef0e608168de952b837b8dd7e90341f015", + "sha256:adb697c0bd35100dc690de83154627fbab1f4f3c0386df266dded865fc50a902", + "sha256:b12c6b18269ca471eedd41c1b6a1065b2f7827508edb9a7ed5555e9a56dcfc97", + "sha256:b9389a429e0e5142e69d5bf4a435dd688c14478a19bb901735cdf75e57b13845", + "sha256:ba9e7484d286cd5a43744e5f47b0b3fb457865baf07bafc6bee91896364e1419", + "sha256:bb5555cff66c4d3d6213a296b360f9e1a8e323e74e0426b6c10ed7f4d021e464", + "sha256:be57b6d56e49c2739cdf776839a92330e933dd5e5d929966fbbd380c77f060be", + "sha256:c69e42c892c018cd3c8d90da61d845f50a8243062b19d228189b0224150018a9", + "sha256:ccc660a77e1c2bf24ddbce969af9447a9474790160cfb23de6be4fa88e3951c7", + "sha256:d5275455b3e4627c8e7154feaf7ee0743c2e7af82f6e3b561967b1cca755a0be", + "sha256:d75cded8a3cff93da9edc31446872d2997e327921d8eed86641efafd350e1df1", + "sha256:d872ec5aeb086cbea771c573600d47944eea2dcba8be5f3ee649bfe3cb8dc9ba", + "sha256:d891c136b5b310d0e702e186d70cd16d1119ea8927347045124cb286b29297e5", + "sha256:db1dab894cc139f67822a92910466531de5ea6034ddfd2b11c0d4c6257168073", + "sha256:e28bf44afa2b187cc9f41749138a64435bf340adfcacb5b2290c070ce99839d4", + "sha256:e5ea1cf0872ee455c03e5674b5bca5e3e68e159379c1af0903e89f5eba9ccc3a", + "sha256:e77363e8425325384f9d49272c54045bbed2f478e9dd698dbc65dbc37860eb0a", + "sha256:ee5defd1733fd6ec08b168bd4f5387d5b322f45ca9e0e6c817ea6c4cd36313e3", + "sha256:f1592791f8204ae9166de22ba7e6705fa4ebd02936c09436a1bb85aabca3e599", + "sha256:f2d1ec60d6d256bdf298cb86b78dd715980828f50c46701abc3b0a2b3f8a0dc0", + "sha256:f3ca78518bc6bc92828cd11867b121891d75cae4ea9e908d72030609b996db1b", + "sha256:f7b15f589593110ae767ce997775d645b47e5cbbf54fd322f8ebea6277466cec", + "sha256:fd1213c86e48dfdc5a0cc676551db467495a95a662d2396ecd58e719191446e1", + "sha256:ff74026a461eb0660366fb01c650c1d00f833a086b336bdad7ab00cc952072b3" ], "index": "pypi", "markers": "python_version >= '3.9'", - "version": "==7.6.8" + "version": "==7.6.9" }, "docker": { "hashes": [ @@ -2222,11 +2220,11 @@ }, "six": { "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", + "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", - "version": "==1.16.0" + "version": "==1.17.0" }, "testcontainers-core": { "hashes": [ @@ -2264,7 +2262,7 @@ "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9" ], - "markers": "python_version >= '3.10'", + "markers": "python_version >= '3.8'", "version": "==2.2.3" }, "wrapt": { diff --git a/dbrepo-analyse-service/lib/dbrepo-1.5.3.tar.gz b/dbrepo-analyse-service/lib/dbrepo-1.5.3.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..2bb796d8fece2d97c3b2168248ff493dfa24a549 GIT binary patch literal 39620 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%0DwaYtdU30g} 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 literal 0 HcmV?d00001 diff --git a/dbrepo-data-service/pom.xml b/dbrepo-data-service/pom.xml index 5f813b94c7..99822586db 100644 --- a/dbrepo-data-service/pom.xml +++ b/dbrepo-data-service/pom.xml @@ -11,7 +11,7 @@ <groupId>at.tuwien</groupId> <artifactId>dbrepo-data-service</artifactId> <name>dbrepo-data-service</name> - <version>1.5.2</version> + <version>1.5.3</version> <description>Service that manages the data</description> @@ -44,7 +44,6 @@ <jwt.version>4.3.0</jwt.version> <opencsv.version>5.7.1</opencsv.version> <super-csv.version>2.4.0</super-csv.version> - <jsql.version>4.6</jsql.version> <springdoc-openapi.version>2.3.0</springdoc-openapi.version> <hsqldb.version>2.7.2</hsqldb.version> <testcontainers.version>1.19.1</testcontainers.version> @@ -64,6 +63,10 @@ </properties> <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-validation</artifactId> + </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> @@ -208,11 +211,6 @@ <artifactId>jackson-datatype-hibernate6</artifactId> <version>${jackson-datatype.version}</version> </dependency> - <dependency> - <groupId>com.github.jsqlparser</groupId> - <artifactId>jsqlparser</artifactId> - <version>${jsql.version}</version> - </dependency> <!-- Authentication --> <dependency> <groupId>com.auth0</groupId> diff --git a/dbrepo-data-service/querystore/pom.xml b/dbrepo-data-service/querystore/pom.xml index a508c88343..1c794f4ef6 100644 --- a/dbrepo-data-service/querystore/pom.xml +++ b/dbrepo-data-service/querystore/pom.xml @@ -6,12 +6,12 @@ <parent> <groupId>at.tuwien</groupId> <artifactId>dbrepo-data-service</artifactId> - <version>1.5.2</version> + <version>1.5.3</version> </parent> <artifactId>dbrepo-data-service-querystore</artifactId> <name>dbrepo-data-service-querystore</name> - <version>1.5.2</version> + <version>1.5.3</version> <dependencies/> diff --git a/dbrepo-data-service/report/pom.xml b/dbrepo-data-service/report/pom.xml index da3d605405..2315b33d48 100644 --- a/dbrepo-data-service/report/pom.xml +++ b/dbrepo-data-service/report/pom.xml @@ -6,12 +6,12 @@ <parent> <groupId>at.tuwien</groupId> <artifactId>dbrepo-data-service</artifactId> - <version>1.5.2</version> + <version>1.5.3</version> </parent> <artifactId>report</artifactId> <name>dbrepo-data-service-report</name> - <version>1.5.2</version> + <version>1.5.3</version> <description> This module is only intended for the pipeline coverage report. See the detailed report in the respective modules diff --git a/dbrepo-data-service/rest-service/pom.xml b/dbrepo-data-service/rest-service/pom.xml index 0859d43724..96b02e1b50 100644 --- a/dbrepo-data-service/rest-service/pom.xml +++ b/dbrepo-data-service/rest-service/pom.xml @@ -6,18 +6,18 @@ <parent> <groupId>at.tuwien</groupId> <artifactId>dbrepo-data-service</artifactId> - <version>1.5.2</version> + <version>1.5.3</version> </parent> <artifactId>rest-service</artifactId> <name>dbrepo-data-service-rest-service</name> - <version>1.5.2</version> + <version>1.5.3</version> <dependencies> <dependency> <groupId>at.tuwien</groupId> <artifactId>services</artifactId> - <version>1.5.2</version> + <version>1.5.3</version> </dependency> </dependencies> 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/AbstractEndpoint.java new file mode 100644 index 0000000000..334128637e --- /dev/null +++ b/dbrepo-data-service/rest-service/src/main/java/at/tuwien/endpoints/AbstractEndpoint.java @@ -0,0 +1,34 @@ +package at.tuwien.endpoints; + +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public abstract class AbstractEndpoint { + + public List<Map<String, Object>> transform(Dataset<Row> dataset) { + return dataset.collectAsList() + .stream() + .map(row -> { + final Map<String, Object> map = new LinkedHashMap<>(); + for (int i = 0; i < dataset.columns().length; i++) { + if (row.get(i) == null) { + map.put(dataset.columns()[i], null); + continue; + } + try { + map.put(dataset.columns()[i], Integer.parseInt(String.valueOf(row.get(i)))); + map.put(dataset.columns()[i], Double.parseDouble(String.valueOf(row.get(i)))); + } catch (NumberFormatException e) { + /* ignore */ + } + map.put(dataset.columns()[i], String.valueOf(row.get(i))); + } + return map; + }) + .toList(); + } +} 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 63962694f1..22b74f9d5a 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,14 +1,18 @@ package at.tuwien.endpoints; import at.tuwien.ExportResourceDto; +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; -import at.tuwien.api.database.query.QueryResultDto; import at.tuwien.api.error.ApiErrorDto; import at.tuwien.exception.*; import at.tuwien.gateway.MetadataServiceGateway; +import at.tuwien.mapper.MetadataMapper; +import at.tuwien.service.SchemaService; +import at.tuwien.service.StorageService; import at.tuwien.service.SubsetService; import at.tuwien.utils.UserUtil; import at.tuwien.validation.EndpointValidator; @@ -25,10 +29,10 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import lombok.extern.log4j.Log4j2; -import org.apache.commons.lang3.RandomStringUtils; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; @@ -38,22 +42,30 @@ import java.security.Principal; import java.sql.SQLException; import java.time.Instant; import java.util.List; +import java.util.Map; import java.util.UUID; @Log4j2 @RestController @CrossOrigin(origins = "*") @RequestMapping(path = "/api/database/{databaseId}/subset") -public class SubsetEndpoint { +public class SubsetEndpoint extends AbstractEndpoint { + private final SchemaService schemaService; private final SubsetService subsetService; + private final MetadataMapper metadataMapper; + private final StorageService storageService; private final EndpointValidator endpointValidator; private final MetadataServiceGateway metadataServiceGateway; @Autowired - public SubsetEndpoint(SubsetService queryService, EndpointValidator endpointValidator, + public SubsetEndpoint(SchemaService schemaService, SubsetService subsetService, MetadataMapper metadataMapper, + StorageService storageService, EndpointValidator endpointValidator, MetadataServiceGateway metadataServiceGateway) { - this.subsetService = queryService; + this.schemaService = schemaService; + this.subsetService = subsetService; + this.metadataMapper = metadataMapper; + this.storageService = storageService; this.endpointValidator = endpointValidator; this.metadataServiceGateway = metadataServiceGateway; } @@ -145,16 +157,15 @@ public class SubsetEndpoint { @NotNull HttpServletRequest httpServletRequest, @RequestParam(required = false) Instant timestamp) throws DatabaseUnavailableException, DatabaseNotFoundException, RemoteUnavailableException, - QueryNotFoundException, FormatNotAvailableException, StorageUnavailableException, QueryMalformedException, - StorageNotFoundException, UserNotFoundException, MetadataServiceException, ViewNotFoundException, - MalformedException { + QueryNotFoundException, FormatNotAvailableException, StorageUnavailableException, UserNotFoundException, + MetadataServiceException, TableNotFoundException, ViewMalformedException, QueryMalformedException { String accept = httpServletRequest.getHeader("Accept"); log.debug("endpoint find subset in database, databaseId={}, subsetId={}, accept={}, timestamp={}", databaseId, subsetId, accept, timestamp); final PrivilegedDatabaseDto database = metadataServiceGateway.getDatabaseById(databaseId); - final QueryDto query; + final QueryDto subset; try { - query = subsetService.findById(database, subsetId); + subset = subsetService.findById(database, subsetId); } 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); @@ -171,21 +182,21 @@ public class SubsetEndpoint { switch (accept) { case MediaType.APPLICATION_JSON_VALUE: log.trace("accept header matches json"); - return ResponseEntity.ok(query); + return ResponseEntity.ok(subset); case "text/csv": log.trace("accept header matches csv"); try { - final ExportResourceDto resource = subsetService.export(database, query, timestamp); + final Dataset<Row> dataset = subsetService.getData(database, subset, null, null); + final ExportResourceDto resource = storageService.transformDataset(dataset); final HttpHeaders headers = new HttpHeaders(); headers.add("Content-Disposition", "attachment; filename=\"" + resource.getFilename() + "\""); log.trace("export table resulted in resource {}", resource); return ResponseEntity.ok() .headers(headers) .body(resource.getResource()); - } 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); + log.error("Failed to find data: {}", e.getMessage()); + throw new DatabaseUnavailableException("Failed to find data: " + e.getMessage(), e); } } throw new FormatNotAvailableException("Must provide either application/json or text/csv headers"); @@ -201,7 +212,7 @@ public class SubsetEndpoint { description = "Created subset", content = {@Content( mediaType = "application/json", - schema = @Schema(implementation = QueryResultDto.class))}), + schema = @Schema(implementation = List.class))}), @ApiResponse(responseCode = "400", description = "Malformed select query", content = {@Content( @@ -233,16 +244,18 @@ public class SubsetEndpoint { mediaType = "application/json", schema = @Schema(implementation = ApiErrorDto.class))}), }) - public ResponseEntity<QueryResultDto> create(@NotNull @PathVariable("databaseId") Long databaseId, - @Valid @RequestBody ExecuteStatementDto data, - Principal principal, - @RequestParam(required = false) Long page, - @RequestParam(required = false) Long size, - @RequestParam(required = false) Instant timestamp) + public ResponseEntity<List<Map<String, Object>>> create(@NotNull @PathVariable("databaseId") Long databaseId, + @Valid @RequestBody ExecuteStatementDto data, + Principal principal, + @NotNull HttpServletRequest request, + @RequestParam(required = false) Instant timestamp, + @RequestParam(required = false) Long page, + @RequestParam(required = false) Long size) throws DatabaseUnavailableException, DatabaseNotFoundException, RemoteUnavailableException, QueryNotFoundException, StorageUnavailableException, QueryMalformedException, StorageNotFoundException, QueryStoreInsertException, TableMalformedException, PaginationException, QueryNotSupportedException, - NotAllowedException, UserNotFoundException, MetadataServiceException { + NotAllowedException, UserNotFoundException, MetadataServiceException, TableNotFoundException, + ViewMalformedException, ViewNotFoundException { log.debug("endpoint create subset in database, databaseId={}, data.statement={}, page={}, size={}, " + "timestamp={}", databaseId, data.getStatement(), page, size, timestamp); @@ -265,17 +278,13 @@ public class SubsetEndpoint { } /* create */ final PrivilegedDatabaseDto database = metadataServiceGateway.getDatabaseById(databaseId); - final QueryResultDto queryResult; try { - queryResult = subsetService.execute(database, data.getStatement(), timestamp, userId, page, size, null, - null); + final Long subsetId = subsetService.create(database, data.getStatement(), timestamp, userId); + return getData(databaseId, subsetId, principal, request, page, size); } 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); } - log.info("Created subset with id: {}", queryResult.getId()); - return ResponseEntity.status(HttpStatus.CREATED) - .body(queryResult); } @RequestMapping(value = "/{subsetId}/data", method = {RequestMethod.GET, RequestMethod.HEAD}) @@ -286,11 +295,13 @@ public class SubsetEndpoint { @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Retrieved subset data", - headers = {@Header(name = "X-Count", description = "Number of rows", schema = @Schema(implementation = Long.class), required = true), - @Header(name = "Access-Control-Expose-Headers", description = "Expose `X-Count` custom header", schema = @Schema(implementation = String.class), required = true)}, + headers = {@Header(name = "X-Count", description = "Number of rows", schema = @Schema(implementation = Long.class)), + @Header(name = "X-Headers", description = "The list of headers separated by comma", schema = @Schema(implementation = String.class)), + @Header(name = "X-Id", description = "The subset id", schema = @Schema(implementation = Long.class), required = true), + @Header(name = "Access-Control-Expose-Headers", description = "Reverse proxy exposing of custom headers", schema = @Schema(implementation = String.class), required = true)}, content = {@Content( mediaType = "application/json", - schema = @Schema(implementation = QueryResultDto.class))}), + schema = @Schema(implementation = List.class))}), @ApiResponse(responseCode = "400", description = "Invalid pagination", content = {@Content( @@ -312,16 +323,16 @@ public class SubsetEndpoint { mediaType = "application/json", schema = @Schema(implementation = ApiErrorDto.class))}), }) - public ResponseEntity<QueryResultDto> getData(@NotNull @PathVariable("databaseId") Long databaseId, - @NotNull @PathVariable("subsetId") Long subsetId, - Principal principal, - @NotNull HttpServletRequest request, - @RequestParam(required = false) Long page, - @RequestParam(required = false) Long size) throws PaginationException, - DatabaseNotFoundException, RemoteUnavailableException, NotAllowedException, QueryNotFoundException, - DatabaseUnavailableException, TableMalformedException, QueryMalformedException, UserNotFoundException, - MetadataServiceException { - log.debug("endpoint re-execute query, databaseId={}, subsetId={}, principal.name={} page={}, size={}", + public ResponseEntity<List<Map<String, Object>>> getData(@NotNull @PathVariable("databaseId") Long databaseId, + @NotNull @PathVariable("subsetId") Long subsetId, + Principal principal, + @NotNull HttpServletRequest request, + @RequestParam(required = false) Long page, + @RequestParam(required = false) Long size) + throws PaginationException, DatabaseNotFoundException, RemoteUnavailableException, NotAllowedException, + QueryNotFoundException, DatabaseUnavailableException, TableMalformedException, QueryMalformedException, + UserNotFoundException, MetadataServiceException, TableNotFoundException, ViewMalformedException, ViewNotFoundException { + 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 = metadataServiceGateway.getDatabaseById(databaseId); @@ -342,21 +353,24 @@ public class SubsetEndpoint { log.debug("size not set: default to {}", size); } try { - final QueryDto query = subsetService.findById(database, subsetId); + final HttpHeaders headers = new HttpHeaders(); + headers.set("X-Id", "" + subsetId); + final QueryDto subset = subsetService.findById(database, subsetId); if (request.getMethod().equals("HEAD")) { - final HttpHeaders headers = new HttpHeaders(); - headers.set("Access-Control-Expose-Headers", "X-Count"); - final Long count = subsetService.reExecuteCount(database, query); + headers.set("Access-Control-Expose-Headers", "X-Count X-Id"); + final Long count = subsetService.reExecuteCount(database, subset); headers.set("X-Count", "" + count); return ResponseEntity.ok() .headers(headers) .build(); } - final QueryResultDto result = subsetService.reExecute(database, query, page, size, null, null); - result.setId(subsetId); - log.trace("re-execute query resulted in result {}", result); + final Dataset<Row> dataset = subsetService.getData(database, subset, page, size); + final ViewDto view = schemaService.inspectView(database, metadataMapper.queryDtoToViewName(subset)); + 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.ok() - .body(result); + .headers(headers) + .body(transform(dataset)); } catch (SQLException e) { log.error("Failed to establish connection to database: {}", e.getMessage()); throw new DatabaseUnavailableException("Failed to establish connection to database: " + e.getMessage(), e); diff --git a/dbrepo-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 2a9beb3d04..54a19850a4 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 @@ -5,14 +5,15 @@ 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.query.QueryResultDto; 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.*; import at.tuwien.gateway.MetadataServiceGateway; import at.tuwien.service.SchemaService; +import at.tuwien.service.StorageService; import at.tuwien.service.TableService; import at.tuwien.utils.UserUtil; import at.tuwien.validation.EndpointValidator; @@ -29,6 +30,8 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import lombok.extern.log4j.Log4j2; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.InputStreamResource; import org.springframework.http.HttpHeaders; @@ -41,23 +44,26 @@ import java.security.Principal; import java.sql.SQLException; import java.time.Instant; import java.util.List; +import java.util.Map; @Log4j2 @RestController @CrossOrigin(origins = "*") @RequestMapping(path = "/api/database/{databaseId}/table") -public class TableEndpoint { +public class TableEndpoint extends AbstractEndpoint { private final TableService tableService; private final SchemaService schemaService; + private final StorageService storageService; private final EndpointValidator endpointValidator; private final MetadataServiceGateway metadataServiceGateway; @Autowired - public TableEndpoint(TableService tableService, SchemaService schemaService, EndpointValidator endpointValidator, - MetadataServiceGateway metadataServiceGateway) { + public TableEndpoint(TableService tableService, SchemaService schemaService, StorageService storageService, + EndpointValidator endpointValidator, MetadataServiceGateway metadataServiceGateway) { this.tableService = tableService; this.schemaService = schemaService; + this.storageService = storageService; this.endpointValidator = endpointValidator; this.metadataServiceGateway = metadataServiceGateway; } @@ -171,7 +177,7 @@ public class TableEndpoint { @Header(name = "Access-Control-Expose-Headers", description = "Expose `X-Count` custom header", schema = @Schema(implementation = String.class), required = true)}, content = {@Content( mediaType = "application/json", - schema = @Schema(implementation = QueryResultDto.class))}), + schema = @Schema(implementation = List.class))}), @ApiResponse(responseCode = "400", description = "Request pagination or table data select query is malformed", content = {@Content( @@ -193,17 +199,16 @@ public class TableEndpoint { mediaType = "application/json", schema = @Schema(implementation = ApiErrorDto.class))}), }) - public ResponseEntity<QueryResultDto> getData(@NotNull @PathVariable("databaseId") Long databaseId, - @NotNull @PathVariable("tableId") Long tableId, - @RequestParam(required = false) Instant timestamp, - @RequestParam(required = false) Long page, - @RequestParam(required = false) Long size, - @NotNull HttpServletRequest request, - Principal principal) + public ResponseEntity<List<Map<String, Object>>> getData(@NotNull @PathVariable("databaseId") Long databaseId, + @NotNull @PathVariable("tableId") Long tableId, + @RequestParam(required = false) Instant timestamp, + @RequestParam(required = false) Long page, + @RequestParam(required = false) Long size, + @NotNull HttpServletRequest request, + Principal principal) throws DatabaseUnavailableException, RemoteUnavailableException, TableNotFoundException, - TableMalformedException, PaginationException, QueryMalformedException, MetadataServiceException, - NotAllowedException { - log.debug("endpoint find table data, databaseId={}, tableId={}, timestamp={}, page={}, size={}", databaseId, + PaginationException, QueryMalformedException, MetadataServiceException, NotAllowedException { + log.debug("endpoint get table data, databaseId={}, tableId={}, timestamp={}, page={}, size={}", databaseId, tableId, timestamp, page, size); endpointValidator.validateDataParams(page, size); /* parameters */ @@ -228,18 +233,21 @@ public class TableEndpoint { metadataServiceGateway.getAccess(databaseId, UserUtil.getId(principal)); } try { + final HttpHeaders headers = new HttpHeaders(); if (request.getMethod().equals("HEAD")) { - final HttpHeaders headers = new HttpHeaders(); headers.set("Access-Control-Expose-Headers", "X-Count"); headers.set("X-Count", "" + tableService.getCount(table, timestamp)); return ResponseEntity.ok() .headers(headers) .build(); } - final QueryResultDto dto = tableService.getPaginatedData(table, timestamp, page, size); + headers.set("Access-Control-Expose-Headers", "X-Headers"); + headers.set("X-Headers", String.join(",", table.getColumns().stream().map(ColumnDto::getInternalName).toList())); return ResponseEntity.ok() - .body(dto); - } catch (SQLException e) { + .headers(headers) + .body(transform(tableService.getData(table.getDatabase(), table.getInternalName(), timestamp, + null, null, null, null))); + } catch (SQLException | QueryMalformedException e) { log.error("Failed to establish connection to database: {}", e.getMessage()); throw new DatabaseUnavailableException("Failed to establish connection to database: " + e.getMessage(), e); } @@ -552,13 +560,14 @@ public class TableEndpoint { @RequestParam(required = false) Instant timestamp, Principal principal) throws RemoteUnavailableException, TableNotFoundException, NotAllowedException, StorageUnavailableException, - QueryMalformedException, MetadataServiceException, MalformedException { + QueryMalformedException, MetadataServiceException { 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); } + // TODO improve to single operation checking if user xyz has access to table abc final PrivilegedTableDto table = metadataServiceGateway.getTableById(databaseId, tableId); if (!table.getIsPublic()) { if (principal == null) { @@ -567,8 +576,10 @@ public class TableEndpoint { } metadataServiceGateway.getAccess(databaseId, UserUtil.getId(principal)); } + final Dataset<Row> dataset = tableService.getData(table.getDatabase(), table.getInternalName(), timestamp, null, + null, null, null); + final ExportResourceDto resource = storageService.transformDataset(dataset); final HttpHeaders headers = new HttpHeaders(); - final ExportResourceDto resource = tableService.exportDataset(table, timestamp); headers.add("Content-Disposition", "attachment; filename=\"" + resource.getFilename() + "\""); log.trace("export table resulted in resource {}", resource); return ResponseEntity.ok() 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 f0b1b891e7..001350246e 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,14 +1,16 @@ package at.tuwien.endpoints; import at.tuwien.ExportResourceDto; +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.database.query.QueryResultDto; import at.tuwien.api.error.ApiErrorDto; import at.tuwien.exception.*; import at.tuwien.gateway.MetadataServiceGateway; +import at.tuwien.service.StorageService; +import at.tuwien.service.TableService; import at.tuwien.service.ViewService; import at.tuwien.utils.UserUtil; import at.tuwien.validation.EndpointValidator; @@ -22,7 +24,6 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; -import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; @@ -37,21 +38,26 @@ import java.security.Principal; import java.sql.SQLException; import java.time.Instant; import java.util.List; +import java.util.Map; @Log4j2 @RestController @CrossOrigin(origins = "*") @RequestMapping(path = "/api/database/{databaseId}/view") -public class ViewEndpoint { +public class ViewEndpoint extends AbstractEndpoint { private final ViewService viewService; + private final TableService tableService; + private final StorageService storageService; private final EndpointValidator endpointValidator; private final MetadataServiceGateway metadataServiceGateway; @Autowired - public ViewEndpoint(ViewService viewService, EndpointValidator endpointValidator, - MetadataServiceGateway metadataServiceGateway) { + public ViewEndpoint(ViewService viewService, TableService tableService, StorageService storageService, + EndpointValidator endpointValidator, MetadataServiceGateway metadataServiceGateway) { this.viewService = viewService; + this.tableService = tableService; + this.storageService = storageService; this.endpointValidator = endpointValidator; this.metadataServiceGateway = metadataServiceGateway; } @@ -188,7 +194,7 @@ public class ViewEndpoint { log.debug("endpoint delete view, databaseId={}, viewId={}", databaseId, viewId); final PrivilegedViewDto view = metadataServiceGateway.getViewById(databaseId, viewId); try { - viewService.delete(view); + viewService.delete(view.getDatabase(), view.getInternalName()); return ResponseEntity.status(HttpStatus.ACCEPTED) .build(); } catch (SQLException e) { @@ -209,7 +215,7 @@ public class ViewEndpoint { @Header(name = "Access-Control-Expose-Headers", description = "Expose `X-Count` custom header", schema = @Schema(implementation = String.class), required = true)}, content = {@Content( mediaType = "application/json", - schema = @Schema(implementation = QueryResultDto.class))}), + schema = @Schema(implementation = List.class))}), @ApiResponse(responseCode = "400", description = "Request pagination is malformed", content = {@Content( @@ -236,16 +242,15 @@ public class ViewEndpoint { mediaType = "application/json", schema = @Schema(implementation = ApiErrorDto.class))}), }) - public ResponseEntity<QueryResultDto> getData(@NotNull @PathVariable("databaseId") Long databaseId, - @NotNull @PathVariable("viewId") Long viewId, - @RequestParam(required = false) Long page, - @RequestParam(required = false) Long size, - @RequestParam(required = false) Instant timestamp, - @NotNull HttpServletRequest request, - Principal principal) - throws DatabaseUnavailableException, RemoteUnavailableException, ViewNotFoundException, - QueryMalformedException, ViewMalformedException, PaginationException, NotAllowedException, - MetadataServiceException { + public ResponseEntity<List<Map<String, Object>>> getData(@NotNull @PathVariable("databaseId") Long databaseId, + @NotNull @PathVariable("viewId") Long viewId, + @RequestParam(required = false) Long page, + @RequestParam(required = false) Long size, + @RequestParam(required = false) Instant timestamp, + @NotNull HttpServletRequest request, + Principal principal) + throws DatabaseUnavailableException, RemoteUnavailableException, ViewNotFoundException, PaginationException, + QueryMalformedException, NotAllowedException, MetadataServiceException, TableNotFoundException { log.debug("endpoint get view data, databaseId={}, viewId={}, page={}, size={}, timestamp={}", databaseId, viewId, page, size, timestamp); endpointValidator.validateDataParams(page, size); @@ -262,23 +267,26 @@ public class ViewEndpoint { timestamp = Instant.now(); log.debug("timestamp not set: default to {}", timestamp); } + // TODO improve with a single operation that checks if user xyz has access to view abc final PrivilegedViewDto view = metadataServiceGateway.getViewById(databaseId, viewId); if (!view.getIsPublic()) { metadataServiceGateway.getAccess(databaseId, UserUtil.getId(principal)); } try { + final HttpHeaders headers = new HttpHeaders(); if (request.getMethod().equals("HEAD")) { - final HttpHeaders headers = new HttpHeaders(); headers.set("Access-Control-Expose-Headers", "X-Count"); headers.set("X-Count", "" + viewService.count(view, timestamp)); return ResponseEntity.ok() .headers(headers) .build(); } - final QueryResultDto result = viewService.data(view, timestamp, page, size); - log.trace("get view data resulted in result {}", result); + headers.set("Access-Control-Expose-Headers", "X-Headers"); + headers.set("X-Headers", String.join(",", view.getColumns().stream().map(ViewColumnDto::getInternalName).toList())); return ResponseEntity.ok() - .body(result); + .headers(headers) + .body(transform(tableService.getData(view.getDatabase(), view.getInternalName(), timestamp, page, + size, null, null))); } 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); @@ -319,11 +327,17 @@ public class ViewEndpoint { }) public ResponseEntity<InputStreamResource> exportDataset(@NotNull @PathVariable("databaseId") Long databaseId, @NotNull @PathVariable("viewId") Long viewId, + @RequestParam(required = false) Instant timestamp, Principal principal) throws RemoteUnavailableException, ViewNotFoundException, NotAllowedException, MetadataServiceException, - StorageUnavailableException, QueryMalformedException, MalformedException { + StorageUnavailableException, QueryMalformedException, TableNotFoundException { log.debug("endpoint export view data, databaseId={}, viewId={}", databaseId, viewId); /* parameters */ + if (timestamp == null) { + timestamp = Instant.now(); + log.debug("timestamp not set: default to {}", timestamp); + } + /* parameters */ final PrivilegedViewDto view = metadataServiceGateway.getViewById(databaseId, viewId); if (!view.getIsPublic()) { if (principal == null) { @@ -332,8 +346,13 @@ public class ViewEndpoint { } metadataServiceGateway.getAccess(databaseId, UserUtil.getId(principal)); } + final List<String> columns = view.getColumns() + .stream() + .map(ViewColumnDto::getInternalName) + .toList(); final HttpHeaders headers = new HttpHeaders(); - final ExportResourceDto resource = viewService.exportDataset(view); + final ExportResourceDto resource = storageService.transformDataset(tableService.getData(view.getDatabase(), + view.getInternalName(), timestamp, null, null, null, null)); headers.add("Content-Disposition", "attachment; filename=\"" + resource.getFilename() + "\""); log.trace("export table resulted in resource {}", resource); return ResponseEntity.ok() diff --git a/dbrepo-data-service/rest-service/src/main/resources/application.yml b/dbrepo-data-service/rest-service/src/main/resources/application.yml index 9bd16e6089..c23c50bad6 100644 --- a/dbrepo-data-service/rest-service/src/main/resources/application.yml +++ b/dbrepo-data-service/rest-service/src/main/resources/application.yml @@ -3,27 +3,26 @@ application: version: '@project.version@' spring: datasource: - url: jdbc:h2:mem:fda;DB_CLOSE_ON_EXIT=FALSE;INIT=CREATE SCHEMA IF NOT EXISTS FDA;NON_KEYWORDS=value + url: "jdbc:h2:mem:dbrepo;DB_CLOSE_ON_EXIT=FALSE;INIT=CREATE SCHEMA IF NOT EXISTS dbrepo;NON_KEYWORDS=value" driver-class-name: org.h2.Driver username: sa password: password - rabbitmq: - host: "${BROKER_HOST:broker-service}" - virtual-host: "${BROKER_VIRTUALHOST:dbrepo}" - password: "${BROKER_PASSWORD:admin}" - username: "${BROKER_USERNAME:admin}" - port: ${BROKER_PORT:5672} jpa: show-sql: false - database-platform: org.hibernate.dialect.H2Dialect open-in-view: false properties: hibernate: - default_schema: fda + default_schema: "${METADATA_DB:dbrepo}" jdbc: time_zone: UTC application: name: data-service + rabbitmq: + host: "${BROKER_HOST:broker-service}" + virtual-host: "${BROKER_VIRTUALHOST:dbrepo}" + username: "${BROKER_USERNAME:admin}" + password: "${BROKER_PASSWORD:admin}" + port: ${BROKER_PORT:5672} main: banner-mode: off management: @@ -65,8 +64,8 @@ dbrepo: jwt: public_key: "${JWT_PUBKEY:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqqnHQ2BWWW9vDNLRCcxD++xZg/16oqMo/c1l+lcFEjjAIJjJp/HqrPYU/U9GvquGE6PbVFtTzW1KcKawOW+FJNOA3CGo8Q1TFEfz43B8rZpKsFbJKvQGVv1Z4HaKPvLUm7iMm8Hv91cLduuoWx6Q3DPe2vg13GKKEZe7UFghF+0T9u8EKzA/XqQ0OiICmsmYPbwvf9N3bCKsB/Y10EYmZRb8IhCoV9mmO5TxgWgiuNeCTtNCv2ePYqL/U0WvyGFW0reasIK8eg3KrAUj8DpyOgPOVBn3lBGf+3KFSYi+0bwZbJZWqbC/Xlk20Go1YfeJPRIt7ImxD27R/lNjgDO/MwIDAQAB}" keycloak: - username: "${AUTH_SERVICE_ADMIN:fda}" - password: "${AUTH_SERVICE_ADMIN_PASSWORD:fda}" + username: "${AUTH_SERVICE_ADMIN:admin}" + password: "${AUTH_SERVICE_ADMIN_PASSWORD:admin}" client: "${AUTH_SERVICE_CLIENT:dbrepo-client}" clientSecret: "${AUTH_SERVICE_CLIENT_SECRET:MUwRc7yfXSJwX8AdRMWaQC3Nep1VjwgG}" sql: @@ -81,4 +80,4 @@ dbrepo: queueName: "${BROKER_QUEUE_NAME:dbrepo}" exchangeName: "${BROKER_EXCHANGE_NAME:dbrepo}" routingKey: "${BROKER_ROUTING_KEY:#}" - connectionTimeout: ${CONNECTION_TIMEOUT:10000} + connectionTimeout: "${SPARQL_CONNECTION_TIMEOUT:10000}" diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/annotations/MockAmqp.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/annotations/MockAmqp.java deleted file mode 100644 index 0f3868c25e..0000000000 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/annotations/MockAmqp.java +++ /dev/null @@ -1,17 +0,0 @@ -package at.tuwien.annotations; - -import at.tuwien.listener.DefaultListener; -import com.rabbitmq.client.Channel; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.boot.test.mock.mockito.MockBeans; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -@MockBeans({@MockBean(Channel.class), @MockBean(DefaultListener.class)}) -public @interface MockAmqp { -} 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 5f302368ee..f1d1b6d795 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,25 +1,27 @@ package at.tuwien.endpoint; -import at.tuwien.ExportResourceDto; 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; -import at.tuwien.api.database.query.QueryResultDto; import at.tuwien.endpoints.SubsetEndpoint; import at.tuwien.exception.*; import at.tuwien.gateway.MetadataServiceGateway; +import at.tuwien.service.SchemaService; +import at.tuwien.service.StorageService; import at.tuwien.service.SubsetService; import at.tuwien.test.AbstractUnitTest; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.log4j.Log4j2; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; +import org.apache.spark.sql.SparkSession; 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.boot.test.mock.mockito.MockBean; -import org.springframework.core.io.InputStreamResource; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -28,13 +30,12 @@ import org.springframework.security.test.context.support.WithAnonymousUser; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.junit.jupiter.SpringExtension; -import java.io.InputStream; import java.sql.SQLException; import java.time.Instant; import java.util.List; +import java.util.Map; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; @Log4j2 @@ -45,12 +46,21 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { @Autowired private SubsetEndpoint subsetEndpoint; + @Autowired + private SparkSession sparkSession; + @MockBean private SubsetService subsetService; + @MockBean + private SchemaService schemaService; + @MockBean private HttpServletRequest httpServletRequest; + @MockBean + private StorageService storageService; + @MockBean private MetadataServiceGateway metadataServiceGateway; @@ -108,8 +118,8 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { @WithAnonymousUser public void findById_succeeds() throws DatabaseNotFoundException, RemoteUnavailableException, UserNotFoundException, DatabaseUnavailableException, StorageUnavailableException, QueryMalformedException, QueryNotFoundException, - FormatNotAvailableException, StorageNotFoundException, SQLException, MetadataServiceException, - ViewNotFoundException, MalformedException { + FormatNotAvailableException, TableNotFoundException, MetadataServiceException, SQLException, + ViewMalformedException { /* mock */ when(metadataServiceGateway.getDatabaseById(DATABASE_3_ID)) @@ -142,19 +152,18 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { @WithAnonymousUser public void findById_acceptCsv_succeeds() throws DatabaseNotFoundException, RemoteUnavailableException, UserNotFoundException, DatabaseUnavailableException, StorageUnavailableException, QueryMalformedException, - QueryNotFoundException, FormatNotAvailableException, StorageNotFoundException, SQLException, - MetadataServiceException, ViewNotFoundException, MalformedException { - final ExportResourceDto mock = ExportResourceDto.builder() - .filename("deadbeef") - .resource(new InputStreamResource(InputStream.nullInputStream())) - .build(); + QueryNotFoundException, FormatNotAvailableException, SQLException, MetadataServiceException, + TableNotFoundException, ViewMalformedException { + final Dataset<Row> mock = sparkSession.emptyDataFrame(); /* mock */ when(metadataServiceGateway.getDatabaseById(DATABASE_3_ID)) .thenReturn(DATABASE_3_PRIVILEGED_DTO); when(subsetService.findById(DATABASE_3_PRIVILEGED_DTO, QUERY_5_ID)) .thenReturn(QUERY_5_DTO); - when(subsetService.export(any(PrivilegedDatabaseDto.class), any(QueryDto.class), any(Instant.class))) + when(storageService.transformDataset(any(Dataset.class))) + .thenReturn(EXPORT_RESOURCE_DTO); + when(subsetService.getData(any(PrivilegedDatabaseDto.class), any(QueryDto.class), eq(null), eq(null))) .thenReturn(mock); /* test */ @@ -165,20 +174,19 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { @WithAnonymousUser public void findById_timestamp_succeeds() throws DatabaseNotFoundException, RemoteUnavailableException, UserNotFoundException, DatabaseUnavailableException, StorageUnavailableException, QueryMalformedException, - QueryNotFoundException, FormatNotAvailableException, StorageNotFoundException, SQLException, - MetadataServiceException, ViewNotFoundException, MalformedException { - final ExportResourceDto mock = ExportResourceDto.builder() - .filename("deadbeef") - .resource(new InputStreamResource(InputStream.nullInputStream())) - .build(); + QueryNotFoundException, FormatNotAvailableException, SQLException, MetadataServiceException, + TableNotFoundException, ViewMalformedException { + final Dataset<Row> mock = sparkSession.emptyDataFrame(); /* mock */ when(metadataServiceGateway.getDatabaseById(DATABASE_3_ID)) .thenReturn(DATABASE_3_PRIVILEGED_DTO); when(subsetService.findById(DATABASE_3_PRIVILEGED_DTO, QUERY_5_ID)) .thenReturn(QUERY_5_DTO); - when(subsetService.export(any(PrivilegedDatabaseDto.class), any(QueryDto.class), any(Instant.class))) + when(subsetService.getData(any(PrivilegedDatabaseDto.class), any(QueryDto.class), eq(null), eq(null))) .thenReturn(mock); + when(storageService.transformDataset(any(Dataset.class))) + .thenReturn(EXPORT_RESOURCE_DTO); /* test */ generic_findById(QUERY_5_ID, MediaType.parseMediaType("text/csv"), Instant.now()); @@ -220,23 +228,19 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { @Test @WithAnonymousUser public void findById_unavailableExport_fails() throws DatabaseNotFoundException, RemoteUnavailableException, - MetadataServiceException, SQLException, StorageUnavailableException, QueryMalformedException, - StorageNotFoundException, UserNotFoundException, QueryNotFoundException, ViewNotFoundException, MalformedException { - final ExportResourceDto mock = ExportResourceDto.builder() - .filename("deadbeef") - .resource(new InputStreamResource(InputStream.nullInputStream())) - .build(); + MetadataServiceException, SQLException, QueryMalformedException, UserNotFoundException, + QueryNotFoundException, TableNotFoundException, ViewMalformedException, StorageUnavailableException { /* mock */ when(metadataServiceGateway.getDatabaseById(DATABASE_3_ID)) .thenReturn(DATABASE_3_PRIVILEGED_DTO); when(subsetService.findById(DATABASE_3_PRIVILEGED_DTO, QUERY_5_ID)) .thenReturn(QUERY_5_DTO); - when(subsetService.export(any(PrivilegedDatabaseDto.class), any(QueryDto.class), any(Instant.class))) - .thenReturn(mock); + when(storageService.transformDataset(any(Dataset.class))) + .thenReturn(EXPORT_RESOURCE_DTO); doThrow(SQLException.class) .when(subsetService) - .export(eq(DATABASE_3_PRIVILEGED_DTO), eq(QUERY_5_DTO), any(Instant.class)); + .getData(eq(DATABASE_3_PRIVILEGED_DTO), eq(QUERY_5_DTO), eq(null), eq(null)); /* test */ assertThrows(DatabaseUnavailableException.class, () -> { @@ -248,9 +252,10 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { @WithMockUser(username = USER_1_USERNAME, authorities = {"execute-query"}) public void create_noAccess_succeeds() throws UserNotFoundException, QueryStoreInsertException, TableMalformedException, NotAllowedException, QueryNotSupportedException, PaginationException, - StorageNotFoundException, DatabaseUnavailableException, StorageUnavailableException, + StorageNotFoundException, DatabaseUnavailableException, StorageUnavailableException, SQLException, QueryMalformedException, QueryNotFoundException, DatabaseNotFoundException, RemoteUnavailableException, - SQLException, MetadataServiceException { + MetadataServiceException, TableNotFoundException, ViewMalformedException, ViewNotFoundException { + final Dataset<Row> mock = sparkSession.emptyDataFrame(); final ExecuteStatementDto request = ExecuteStatementDto.builder() .statement(QUERY_5_STATEMENT) .build(); @@ -258,11 +263,17 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getDatabaseById(DATABASE_3_ID)) .thenReturn(DATABASE_3_PRIVILEGED_DTO); - when(subsetService.execute(eq(DATABASE_3_PRIVILEGED_DTO), anyString(), any(Instant.class), eq(USER_1_ID), eq(0L), eq(10L), eq(null), eq(null))) - .thenReturn(QUERY_5_RESULT_DTO); + when(subsetService.getData(eq(DATABASE_3_PRIVILEGED_DTO), any(QueryDto.class), eq(0L), eq(10L))) + .thenReturn(mock); + when(subsetService.findById(eq(DATABASE_3_PRIVILEGED_DTO), anyLong())) + .thenReturn(QUERY_5_DTO); + when(schemaService.inspectView(eq(DATABASE_3_PRIVILEGED_DTO), anyString())) + .thenReturn(VIEW_5_DTO); + when(httpServletRequest.getMethod()) + .thenReturn("POST"); /* test */ - subsetEndpoint.create(DATABASE_3_ID, request, USER_1_PRINCIPAL, 0L, 10L, null); + subsetEndpoint.create(DATABASE_3_ID, request, USER_1_PRINCIPAL, httpServletRequest, null, 0L, 10L); } @Test @@ -272,9 +283,13 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { .statement("SELECT COUNT(id) FROM tbl") .build(); + /* mock */ + when(httpServletRequest.getMethod()) + .thenReturn("POST"); + /* test */ assertThrows(QueryNotSupportedException.class, () -> { - subsetEndpoint.create(DATABASE_3_ID, request, USER_1_PRINCIPAL, 0L, 10L, null); + subsetEndpoint.create(DATABASE_3_ID, request, USER_1_PRINCIPAL, httpServletRequest, null, 0L, 10L); }); } @@ -284,7 +299,9 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { TableMalformedException, NotAllowedException, QueryNotSupportedException, PaginationException, StorageNotFoundException, DatabaseUnavailableException, StorageUnavailableException, QueryMalformedException, QueryNotFoundException, DatabaseNotFoundException, RemoteUnavailableException, - SQLException, MetadataServiceException { + SQLException, MetadataServiceException, TableNotFoundException, ViewMalformedException, + ViewNotFoundException { + final Dataset<Row> mock = sparkSession.emptyDataFrame(); final ExecuteStatementDto request = ExecuteStatementDto.builder() .statement(QUERY_5_STATEMENT) .build(); @@ -292,18 +309,24 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getDatabaseById(DATABASE_3_ID)) .thenReturn(DATABASE_3_PRIVILEGED_DTO); - when(subsetService.execute(eq(DATABASE_3_PRIVILEGED_DTO), anyString(), any(Instant.class), eq(USER_1_ID), eq(0L), eq(10L), eq(null), eq(null))) - .thenReturn(QUERY_5_RESULT_DTO); + when(schemaService.inspectView(eq(DATABASE_3_PRIVILEGED_DTO), anyString())) + .thenReturn(VIEW_5_DTO); + when(subsetService.findById(eq(DATABASE_3_PRIVILEGED_DTO), anyLong())) + .thenReturn(QUERY_5_DTO); + when(subsetService.getData(eq(DATABASE_3_PRIVILEGED_DTO), any(QueryDto.class), eq(0L), eq(10L))) + .thenReturn(mock); + when(httpServletRequest.getMethod()) + .thenReturn("POST"); /* test */ - subsetEndpoint.create(DATABASE_3_ID, request, USER_1_PRINCIPAL, null, null, null); + subsetEndpoint.create(DATABASE_3_ID, request, USER_1_PRINCIPAL, httpServletRequest, null, null, null); } @Test @WithMockUser(username = USER_1_USERNAME, authorities = {"execute-query"}) - public void create_unavailable_succeeds() throws UserNotFoundException, QueryStoreInsertException, - TableMalformedException, NotAllowedException, QueryNotFoundException, DatabaseNotFoundException, - RemoteUnavailableException, SQLException, MetadataServiceException { + public void create_unavailable_succeeds() throws DatabaseNotFoundException, RemoteUnavailableException, + SQLException, MetadataServiceException, QueryMalformedException, TableNotFoundException, + ViewMalformedException, UserNotFoundException, QueryNotFoundException, QueryStoreInsertException { final ExecuteStatementDto request = ExecuteStatementDto.builder() .statement(QUERY_5_STATEMENT) .build(); @@ -313,11 +336,18 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { .thenReturn(DATABASE_3_PRIVILEGED_DTO); doThrow(SQLException.class) .when(subsetService) - .execute(eq(DATABASE_3_PRIVILEGED_DTO), anyString(), any(Instant.class), eq(USER_1_ID), eq(0L), eq(10L), eq(null), eq(null)); + .getData(eq(DATABASE_3_PRIVILEGED_DTO), any(QueryDto.class), eq(null), eq(null)); + when(subsetService.findById(eq(DATABASE_3_PRIVILEGED_DTO), anyLong())) + .thenReturn(QUERY_5_DTO); + doThrow(SQLException.class) + .when(subsetService) + .create(eq(DATABASE_3_PRIVILEGED_DTO), eq(QUERY_5_STATEMENT), any(Instant.class), eq(USER_1_ID)); + when(httpServletRequest.getMethod()) + .thenReturn("POST"); /* test */ assertThrows(DatabaseUnavailableException.class, () -> { - subsetEndpoint.create(DATABASE_3_ID, request, USER_1_PRINCIPAL, null, null, null); + subsetEndpoint.create(DATABASE_3_ID, request, USER_1_PRINCIPAL, httpServletRequest, null, null, null); }); } @@ -333,10 +363,12 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { doThrow(DatabaseNotFoundException.class) .when(metadataServiceGateway) .getDatabaseById(DATABASE_3_ID); + when(httpServletRequest.getMethod()) + .thenReturn("POST"); /* test */ assertThrows(DatabaseNotFoundException.class, () -> { - subsetEndpoint.create(DATABASE_3_ID, request, USER_1_PRINCIPAL, null, null, null); + subsetEndpoint.create(DATABASE_3_ID, request, USER_1_PRINCIPAL, httpServletRequest, null, null, null); }); } @@ -346,7 +378,8 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { MetadataServiceException, UserNotFoundException, QueryStoreInsertException, TableMalformedException, NotAllowedException, SQLException, QueryNotFoundException, DatabaseUnavailableException, StorageUnavailableException, QueryMalformedException, QueryNotSupportedException, PaginationException, - StorageNotFoundException { + StorageNotFoundException, TableNotFoundException, ViewMalformedException, ViewNotFoundException { + final Dataset<Row> mock = sparkSession.emptyDataFrame(); final ExecuteStatementDto request = ExecuteStatementDto.builder() .statement(QUERY_5_STATEMENT) .build(); @@ -354,11 +387,17 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getDatabaseById(DATABASE_3_ID)) .thenReturn(DATABASE_3_PRIVILEGED_DTO); - when(subsetService.execute(eq(DATABASE_3_PRIVILEGED_DTO), anyString(), any(Instant.class), eq(USER_4_ID), eq(0L), eq(10L), eq(null), eq(null))) - .thenReturn(QUERY_5_RESULT_DTO); + when(subsetService.getData(eq(DATABASE_3_PRIVILEGED_DTO), any(QueryDto.class), eq(0L), eq(10L))) + .thenReturn(mock); + when(subsetService.findById(eq(DATABASE_3_PRIVILEGED_DTO), anyLong())) + .thenReturn(QUERY_5_DTO); + when(schemaService.inspectView(eq(DATABASE_3_PRIVILEGED_DTO), anyString())) + .thenReturn(VIEW_5_DTO); + when(httpServletRequest.getMethod()) + .thenReturn("POST"); /* test */ - subsetEndpoint.create(DATABASE_3_ID, request, USER_4_PRINCIPAL, null, null, null); + subsetEndpoint.create(DATABASE_3_ID, request, USER_4_PRINCIPAL, httpServletRequest, null, null, null); } @Test @@ -367,7 +406,8 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { MetadataServiceException, UserNotFoundException, QueryStoreInsertException, TableMalformedException, NotAllowedException, SQLException, QueryNotFoundException, DatabaseUnavailableException, StorageUnavailableException, QueryMalformedException, QueryNotSupportedException, PaginationException, - StorageNotFoundException { + StorageNotFoundException, TableNotFoundException, ViewMalformedException, ViewNotFoundException { + final Dataset<Row> mock = sparkSession.emptyDataFrame(); final ExecuteStatementDto request = ExecuteStatementDto.builder() .statement(QUERY_5_STATEMENT) .build(); @@ -375,17 +415,25 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { /* mock */ when(metadataServiceGateway.getDatabaseById(DATABASE_3_ID)) .thenReturn(DATABASE_3_PRIVILEGED_DTO); - when(subsetService.execute(eq(DATABASE_3_PRIVILEGED_DTO), anyString(), any(Instant.class), eq(null), eq(0L), eq(10L), eq(null), eq(null))) - .thenReturn(QUERY_5_RESULT_DTO); + when(subsetService.findById(eq(DATABASE_3_PRIVILEGED_DTO), anyLong())) + .thenReturn(QUERY_5_DTO); + when(subsetService.getData(eq(DATABASE_3_PRIVILEGED_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("POST"); /* test */ - subsetEndpoint.create(DATABASE_3_ID, request, null, null, null, null); + subsetEndpoint.create(DATABASE_3_ID, request, null, httpServletRequest, null, null, null); } @Test public void getData_succeeds() throws DatabaseNotFoundException, RemoteUnavailableException, UserNotFoundException, NotAllowedException, SQLException, QueryNotFoundException, TableMalformedException, QueryMalformedException, - DatabaseUnavailableException, PaginationException, MetadataServiceException { + DatabaseUnavailableException, PaginationException, MetadataServiceException, TableNotFoundException, + ViewMalformedException, ViewNotFoundException { + final Dataset<Row> mock = sparkSession.emptyDataFrame(); /* mock */ when(metadataServiceGateway.getDatabaseById(DATABASE_3_ID)) @@ -394,21 +442,24 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { .thenReturn(QUERY_5_DTO); when(subsetService.reExecuteCount(DATABASE_3_PRIVILEGED_DTO, QUERY_5_DTO)) .thenReturn(QUERY_5_RESULT_NUMBER); - when(subsetService.reExecute(DATABASE_3_PRIVILEGED_DTO, QUERY_5_DTO, 0L, 10L, null, null)) - .thenReturn(QUERY_5_RESULT_DTO); + when(subsetService.getData(eq(DATABASE_3_PRIVILEGED_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"); /* test */ - final ResponseEntity<QueryResultDto> response = subsetEndpoint.getData(DATABASE_3_ID, QUERY_5_ID, null, httpServletRequest, null, null); + final ResponseEntity<List<Map<String, Object>>> response = subsetEndpoint.getData(DATABASE_3_ID, QUERY_5_ID, null, httpServletRequest, null, null); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getBody()); } @Test - public void getData_head_succeeds() throws DatabaseNotFoundException, RemoteUnavailableException, UserNotFoundException, - NotAllowedException, SQLException, QueryNotFoundException, TableMalformedException, QueryMalformedException, - DatabaseUnavailableException, PaginationException, MetadataServiceException { + public void getData_head_succeeds() throws DatabaseNotFoundException, RemoteUnavailableException, + UserNotFoundException, NotAllowedException, SQLException, QueryNotFoundException, TableMalformedException, + QueryMalformedException, DatabaseUnavailableException, PaginationException, MetadataServiceException, + TableNotFoundException, ViewMalformedException, ViewNotFoundException { /* mock */ when(metadataServiceGateway.getDatabaseById(DATABASE_3_ID)) @@ -421,7 +472,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { .thenReturn("HEAD"); /* test */ - final ResponseEntity<QueryResultDto> response = subsetEndpoint.getData(DATABASE_3_ID, QUERY_5_ID, null, httpServletRequest, null, null); + final ResponseEntity<List<Map<String, Object>>> response = subsetEndpoint.getData(DATABASE_3_ID, QUERY_5_ID, null, httpServletRequest, null, null); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getHeaders().get("X-Count")); assertEquals(1, response.getHeaders().get("X-Count").size()); @@ -432,22 +483,26 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { @WithMockUser(username = USER_1_USERNAME) public void getData_private_succeeds() throws DatabaseNotFoundException, RemoteUnavailableException, UserNotFoundException, DatabaseUnavailableException, NotAllowedException, TableMalformedException, - QueryMalformedException, QueryNotFoundException, PaginationException, SQLException, MetadataServiceException { + QueryMalformedException, QueryNotFoundException, PaginationException, SQLException, + MetadataServiceException, TableNotFoundException, ViewMalformedException, ViewNotFoundException { + final Dataset<Row> mock = sparkSession.emptyDataFrame(); /* mock */ when(metadataServiceGateway.getDatabaseById(DATABASE_1_ID)) .thenReturn(DATABASE_1_PRIVILEGED_DTO); - when(httpServletRequest.getMethod()) - .thenReturn("GET"); when(subsetService.findById(DATABASE_1_PRIVILEGED_DTO, QUERY_1_ID)) .thenReturn(QUERY_1_DTO); when(subsetService.reExecuteCount(DATABASE_1_PRIVILEGED_DTO, QUERY_1_DTO)) .thenReturn(QUERY_1_RESULT_NUMBER); - when(subsetService.reExecute(DATABASE_1_PRIVILEGED_DTO, QUERY_1_DTO, 0L, 10L, null, null)) - .thenReturn(QUERY_1_RESULT_DTO); + when(subsetService.getData(DATABASE_1_PRIVILEGED_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"); /* test */ - final ResponseEntity<QueryResultDto> response = subsetEndpoint.getData(DATABASE_1_ID, QUERY_1_ID, USER_1_PRINCIPAL, httpServletRequest, null, null); + final ResponseEntity<List<Map<String, Object>>> response = subsetEndpoint.getData(DATABASE_1_ID, QUERY_1_ID, USER_1_PRINCIPAL, httpServletRequest, null, null); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getBody()); } @@ -489,7 +544,8 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { @WithMockUser(username = USER_1_USERNAME) public void getData_privateHead_succeeds() throws DatabaseNotFoundException, RemoteUnavailableException, UserNotFoundException, DatabaseUnavailableException, NotAllowedException, TableMalformedException, - QueryMalformedException, QueryNotFoundException, PaginationException, SQLException, MetadataServiceException { + QueryMalformedException, QueryNotFoundException, PaginationException, SQLException, + MetadataServiceException, TableNotFoundException, ViewMalformedException, ViewNotFoundException { /* mock */ when(metadataServiceGateway.getDatabaseById(DATABASE_1_ID)) @@ -502,7 +558,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { .thenReturn("HEAD"); /* test */ - final ResponseEntity<QueryResultDto> response = subsetEndpoint.getData(DATABASE_1_ID, QUERY_1_ID, USER_1_PRINCIPAL, httpServletRequest, null, null); + final ResponseEntity<List<Map<String, Object>>> response = subsetEndpoint.getData(DATABASE_1_ID, QUERY_1_ID, USER_1_PRINCIPAL, httpServletRequest, null, null); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getHeaders().get("X-Count")); assertEquals(1, response.getHeaders().get("X-Count").size()); @@ -512,8 +568,8 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { @Test @WithMockUser(username = USER_1_USERNAME) public void getData_unavailable_fails() throws DatabaseNotFoundException, RemoteUnavailableException, SQLException, - UserNotFoundException, NotAllowedException, TableMalformedException, QueryNotFoundException, - MetadataServiceException { + UserNotFoundException, QueryNotFoundException, MetadataServiceException, QueryMalformedException, + TableNotFoundException, ViewMalformedException { /* mock */ when(metadataServiceGateway.getDatabaseById(DATABASE_1_ID)) @@ -524,7 +580,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { .thenReturn("GET"); doThrow(SQLException.class) .when(subsetService) - .reExecute(DATABASE_1_PRIVILEGED_DTO, QUERY_1_DTO, 0L, 10L, null, null); + .getData(DATABASE_1_PRIVILEGED_DTO, QUERY_1_DTO, 0L, 10L); /* test */ assertThrows(DatabaseUnavailableException.class, () -> { @@ -654,7 +710,7 @@ public class SubsetEndpointUnitTest extends AbstractUnitTest { protected void generic_findById(Long subsetId, MediaType accept, Instant timestamp) throws UserNotFoundException, DatabaseUnavailableException, StorageUnavailableException, QueryMalformedException, QueryNotFoundException, DatabaseNotFoundException, RemoteUnavailableException, FormatNotAvailableException, - StorageNotFoundException, MetadataServiceException, ViewNotFoundException, MalformedException { + MetadataServiceException, TableNotFoundException, ViewMalformedException, SQLException { /* mock */ when(mockHttpServletRequest.getHeader("Accept")) 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 f13b02caa2..66f06786fb 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,9 +1,7 @@ package at.tuwien.endpoint; -import at.tuwien.ExportResourceDto; import at.tuwien.api.database.DatabaseAccessDto; import at.tuwien.api.database.query.ImportDto; -import at.tuwien.api.database.query.QueryResultDto; import at.tuwien.api.database.table.*; import at.tuwien.api.database.table.internal.PrivilegedTableDto; import at.tuwien.endpoints.TableEndpoint; @@ -14,6 +12,9 @@ import at.tuwien.service.TableService; import at.tuwien.test.AbstractUnitTest; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.log4j.Log4j2; +import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.Row; +import org.apache.spark.sql.SparkSession; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -31,11 +32,11 @@ import org.springframework.security.test.context.support.WithAnonymousUser; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.junit.jupiter.SpringExtension; -import java.io.InputStream; import java.sql.SQLException; import java.time.Instant; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.*; @@ -50,6 +51,9 @@ public class TableEndpointUnitTest extends AbstractUnitTest { private TableEndpoint tableEndpoint; @Autowired + private SparkSession sparkSession; + + @MockBean private HttpServletRequest httpServletRequest; @MockBean @@ -269,41 +273,43 @@ public class TableEndpointUnitTest extends AbstractUnitTest { @Test @WithAnonymousUser - public void getData_succeeds() throws DatabaseUnavailableException, TableNotFoundException, TableMalformedException, - SQLException, QueryMalformedException, RemoteUnavailableException, PaginationException, MetadataServiceException, - NotAllowedException { + public void getData_succeeds() throws DatabaseUnavailableException, TableNotFoundException, QueryMalformedException, + RemoteUnavailableException, PaginationException, MetadataServiceException, NotAllowedException { + final Dataset<Row> mock = sparkSession.emptyDataFrame(); /* mock */ when(metadataServiceGateway.getTableById(DATABASE_3_ID, TABLE_8_ID)) .thenReturn(TABLE_8_PRIVILEGED_DTO); - when(tableService.getPaginatedData(eq(TABLE_8_PRIVILEGED_DTO), any(Instant.class), eq(0L), eq(10L))) - .thenReturn(TABLE_8_DATA_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(mock); + when(httpServletRequest.getMethod()) + .thenReturn("GET"); /* test */ - final ResponseEntity<QueryResultDto> response = tableEndpoint.getData(DATABASE_3_ID, TABLE_8_ID, null, null, null, httpServletRequest, null); + final ResponseEntity<List<Map<String, Object>>> response = tableEndpoint.getData(DATABASE_3_ID, TABLE_8_ID, null, null, null, httpServletRequest, null); assertEquals(HttpStatus.OK, response.getStatusCode()); } @Test @WithAnonymousUser - public void getData_head_succeeds() throws DatabaseUnavailableException, TableNotFoundException, TableMalformedException, - SQLException, QueryMalformedException, RemoteUnavailableException, PaginationException, MetadataServiceException, - NotAllowedException { - final HttpServletRequest mock = mock(HttpServletRequest.class); + public void getData_head_succeeds() throws DatabaseUnavailableException, TableNotFoundException, + SQLException, QueryMalformedException, RemoteUnavailableException, PaginationException, + MetadataServiceException, NotAllowedException { + final Dataset<Row> mock = sparkSession.emptyDataFrame(); /* mock */ when(metadataServiceGateway.getTableById(DATABASE_3_ID, TABLE_8_ID)) .thenReturn(TABLE_8_PRIVILEGED_DTO); - when(mock.getMethod()) - .thenReturn("HEAD"); when(tableService.getCount(eq(TABLE_8_PRIVILEGED_DTO), any(Instant.class))) .thenReturn(3L); - when(tableService.getPaginatedData(eq(TABLE_8_PRIVILEGED_DTO), any(Instant.class), eq(0L), eq(10L))) - .thenReturn(TABLE_8_DATA_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(mock); + when(httpServletRequest.getMethod()) + .thenReturn("HEAD"); /* test */ - final ResponseEntity<QueryResultDto> response = tableEndpoint.getData(DATABASE_3_ID, TABLE_8_ID, null, null, null, mock, null); + final ResponseEntity<List<Map<String, Object>>> response = tableEndpoint.getData(DATABASE_3_ID, TABLE_8_ID, null, null, null, httpServletRequest, null); assertEquals(HttpStatus.OK, response.getStatusCode()); assertNotNull(response.getHeaders().get("Access-Control-Expose-Headers")); assertEquals("X-Count", response.getHeaders().get("Access-Control-Expose-Headers").get(0)); @@ -348,14 +354,16 @@ public class TableEndpointUnitTest extends AbstractUnitTest { @Test @WithAnonymousUser public void getData_unavailable_fails() throws TableNotFoundException, RemoteUnavailableException, - MetadataServiceException, TableMalformedException, SQLException { + MetadataServiceException, QueryMalformedException { /* mock */ when(metadataServiceGateway.getTableById(DATABASE_3_ID, TABLE_8_ID)) .thenReturn(TABLE_8_PRIVILEGED_DTO); - doThrow(SQLException.class) + doThrow(QueryMalformedException.class) .when(tableService) - .getPaginatedData(eq(TABLE_8_PRIVILEGED_DTO), any(Instant.class), eq(0L), eq(10L)); + .getData(eq(DATABASE_3_PRIVILEGED_DTO), eq(TABLE_8_INTERNAL_NAME), any(Instant.class), eq(null), eq(null), eq(null), eq(null)); + when(httpServletRequest.getMethod()) + .thenReturn("GET"); /* test */ assertThrows(DatabaseUnavailableException.class, () -> { @@ -385,19 +393,22 @@ public class TableEndpointUnitTest extends AbstractUnitTest { @WithMockUser(username = USER_2_USERNAME) @MethodSource("anyAccess_parameters") public void getData_private_succeeds(String name, DatabaseAccessDto access) throws DatabaseUnavailableException, - TableNotFoundException, TableMalformedException, SQLException, QueryMalformedException, - RemoteUnavailableException, PaginationException, MetadataServiceException, NotAllowedException { + TableNotFoundException, QueryMalformedException, RemoteUnavailableException, PaginationException, + MetadataServiceException, NotAllowedException { + final Dataset<Row> mock = sparkSession.emptyDataFrame(); /* mock */ when(metadataServiceGateway.getTableById(DATABASE_1_ID, TABLE_1_ID)) .thenReturn(TABLE_1_PRIVILEGED_DTO); when(metadataServiceGateway.getAccess(DATABASE_1_ID, USER_2_ID)) .thenReturn(access); - when(tableService.getPaginatedData(eq(TABLE_1_PRIVILEGED_DTO), any(Instant.class), eq(0L), eq(10L))) - .thenReturn(TABLE_1_DATA_DTO); + when(tableService.getData(eq(DATABASE_1_PRIVILEGED_DTO), eq(TABLE_1_INTERNAL_NAME), any(Instant.class), eq(null), eq(null), eq(null), eq(null))) + .thenReturn(mock); + when(httpServletRequest.getMethod()) + .thenReturn("GET"); /* test */ - final ResponseEntity<QueryResultDto> response = tableEndpoint.getData(DATABASE_1_ID, TABLE_1_ID, null, null, null, httpServletRequest, USER_2_PRINCIPAL); + final ResponseEntity<List<Map<String, Object>>> response = tableEndpoint.getData(DATABASE_1_ID, TABLE_1_ID, null, null, null, httpServletRequest, USER_2_PRINCIPAL); assertEquals(HttpStatus.OK, response.getStatusCode()); } @@ -1125,24 +1136,19 @@ public class TableEndpointUnitTest extends AbstractUnitTest { @Test @WithAnonymousUser - public void exportData_succeeds() throws DatabaseUnavailableException, TableNotFoundException, NotAllowedException, - StorageUnavailableException, QueryMalformedException, RemoteUnavailableException, StorageNotFoundException, - SQLException, MetadataServiceException, MalformedException { - final ExportResourceDto mock = ExportResourceDto.builder() - .filename("deadbeef") - .resource(new InputStreamResource(InputStream.nullInputStream())) - .build(); + public void exportData_succeeds() throws TableNotFoundException, NotAllowedException, + StorageUnavailableException, QueryMalformedException, RemoteUnavailableException, MetadataServiceException { + final Dataset<Row> mock = sparkSession.emptyDataFrame(); /* mock */ when(metadataServiceGateway.getTableById(DATABASE_3_ID, TABLE_8_ID)) .thenReturn(TABLE_8_PRIVILEGED_DTO); - when(tableService.exportDataset(eq(TABLE_8_PRIVILEGED_DTO), any(Instant.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))) .thenReturn(mock); /* test */ final ResponseEntity<InputStreamResource> response = tableEndpoint.exportDataset(DATABASE_3_ID, TABLE_8_ID, null, null); assertEquals(HttpStatus.OK, response.getStatusCode()); - } @ParameterizedTest @@ -1150,24 +1156,20 @@ public class TableEndpointUnitTest extends AbstractUnitTest { @MethodSource("anyAccess_parameters") public void exportData_private_succeeds(String name, DatabaseAccessDto access) throws TableNotFoundException, NotAllowedException, StorageUnavailableException, QueryMalformedException, RemoteUnavailableException, - MetadataServiceException, MalformedException { - final ExportResourceDto mock = ExportResourceDto.builder() - .filename("deadbeef") - .resource(new InputStreamResource(InputStream.nullInputStream())) - .build(); + MetadataServiceException { + final Dataset<Row> mock = sparkSession.emptyDataFrame(); /* mock */ when(metadataServiceGateway.getTableById(DATABASE_1_ID, TABLE_1_ID)) .thenReturn(TABLE_1_PRIVILEGED_DTO); when(metadataServiceGateway.getAccess(DATABASE_1_ID, USER_2_ID)) .thenReturn(access); - when(tableService.exportDataset(eq(TABLE_1_PRIVILEGED_DTO), any(Instant.class))) + when(tableService.getData(eq(DATABASE_1_PRIVILEGED_DTO), eq(TABLE_1_INTERNAL_NAME), any(Instant.class), eq(null), eq(null), eq(null), eq(null))) .thenReturn(mock); /* test */ final ResponseEntity<InputStreamResource> response = tableEndpoint.exportDataset(DATABASE_1_ID, TABLE_1_ID, null, USER_2_PRINCIPAL); assertEquals(HttpStatus.OK, response.getStatusCode()); - } @Test 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 2e856b2d2f..8062de7b45 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 @@ -1,7 +1,6 @@ package at.tuwien.endpoint; import at.tuwien.api.database.ViewDto; -import at.tuwien.api.database.query.QueryResultDto; import at.tuwien.endpoints.ViewEndpoint; import at.tuwien.exception.*; import at.tuwien.gateway.MetadataServiceGateway; @@ -22,10 +21,10 @@ import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.junit.jupiter.SpringExtension; import java.sql.SQLException; -import java.time.Instant; import java.util.List; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.*; @Log4j2 @@ -189,15 +188,15 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { @Test @WithMockUser(username = USER_LOCAL_ADMIN_USERNAME, authorities = {"system"}) - public void delete_succeeds() throws DatabaseNotFoundException, RemoteUnavailableException, ViewMalformedException, - SQLException, DatabaseUnavailableException, ViewNotFoundException, MetadataServiceException { + public void delete_succeeds() throws RemoteUnavailableException, ViewMalformedException, ViewNotFoundException, + SQLException, DatabaseUnavailableException, MetadataServiceException { /* mock */ - when(metadataServiceGateway.getDatabaseById(DATABASE_1_ID)) - .thenReturn(DATABASE_1_PRIVILEGED_DTO); + when(metadataServiceGateway.getViewById(DATABASE_1_ID, VIEW_1_ID)) + .thenReturn(VIEW_1_PRIVILEGED_DTO); doNothing() .when(viewService) - .delete(VIEW_1_PRIVILEGED_DTO); + .delete(DATABASE_1_PRIVILEGED_DTO, VIEW_1_INTERNAL_NAME); /* test */ final ResponseEntity<Void> response = viewEndpoint.delete(DATABASE_1_ID, VIEW_1_ID); @@ -214,7 +213,7 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { .thenReturn(VIEW_1_PRIVILEGED_DTO); doThrow(SQLException.class) .when(viewService) - .delete(VIEW_1_PRIVILEGED_DTO); + .delete(DATABASE_1_PRIVILEGED_DTO, VIEW_1_INTERNAL_NAME); /* test */ assertThrows(DatabaseUnavailableException.class, () -> { @@ -232,7 +231,7 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { .thenReturn(DATABASE_1_PRIVILEGED_DTO); doNothing() .when(viewService) - .delete(VIEW_1_PRIVILEGED_DTO); + .delete(DATABASE_1_PRIVILEGED_DTO, VIEW_1_INTERNAL_NAME); /* test */ assertThrows(org.springframework.security.access.AccessDeniedException.class, () -> { @@ -256,77 +255,77 @@ 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, - SQLException, DatabaseUnavailableException, QueryMalformedException, PaginationException, - NotAllowedException, MetadataServiceException { - - /* mock */ - when(metadataServiceGateway.getViewById(DATABASE_1_ID, VIEW_1_ID)) - .thenReturn(VIEW_1_PRIVILEGED_DTO); - when(metadataServiceGateway.getAccess(DATABASE_1_ID, USER_1_ID)) - .thenReturn(DATABASE_1_USER_1_READ_ACCESS_DTO); - when(httpServletRequest.getMethod()) - .thenReturn("GET"); - when(viewService.data(eq(VIEW_1_PRIVILEGED_DTO), any(Instant.class), eq(0L), eq(10L))) - .thenReturn(VIEW_1_DATA_DTO); - - /* test */ - final ResponseEntity<QueryResultDto> response = viewEndpoint.getData(DATABASE_1_ID, VIEW_1_ID, null, null, null, httpServletRequest, USER_1_PRINCIPAL); - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertNotNull(response.getBody()); - } - - @Test - @WithMockUser(username = USER_1_USERNAME, authorities = {"view-database-view-data"}) - public void getData_privateHead_succeeds() throws RemoteUnavailableException, ViewNotFoundException, - ViewMalformedException, SQLException, DatabaseUnavailableException, QueryMalformedException, - PaginationException, NotAllowedException, MetadataServiceException { - - /* mock */ - when(metadataServiceGateway.getViewById(DATABASE_1_ID, VIEW_3_ID)) - .thenReturn(VIEW_3_PRIVILEGED_DTO); - when(metadataServiceGateway.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))) - .thenReturn(VIEW_3_DATA_COUNT); - - /* test */ - final ResponseEntity<QueryResultDto> response = viewEndpoint.getData(DATABASE_1_ID, VIEW_3_ID, null, null, null, httpServletRequest, USER_1_PRINCIPAL); - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertNotNull(response.getHeaders().get("X-Count")); - assertEquals(1, response.getHeaders().get("X-Count").size()); - assertEquals(VIEW_3_DATA_COUNT, Long.parseLong(response.getHeaders().get("X-Count").get(0))); - assertNotNull(response.getHeaders().get("Access-Control-Expose-Headers")); - assertEquals(1, response.getHeaders().get("Access-Control-Expose-Headers").size()); - assertEquals("X-Count", response.getHeaders().get("Access-Control-Expose-Headers").get(0)); - assertNull(response.getBody()); - } - - @Test - @WithMockUser(username = USER_1_USERNAME, authorities = {"view-database-view-data"}) - public void getData_unavailable_fails() throws RemoteUnavailableException, ViewNotFoundException, SQLException, - ViewMalformedException, NotAllowedException, MetadataServiceException { - - /* mock */ - when(metadataServiceGateway.getViewById(DATABASE_1_ID, VIEW_1_ID)) - .thenReturn(VIEW_1_PRIVILEGED_DTO); - when(metadataServiceGateway.getAccess(DATABASE_1_ID, USER_1_ID)) - .thenReturn(DATABASE_1_USER_1_READ_ACCESS_DTO); - when(httpServletRequest.getMethod()) - .thenReturn("GET"); - doThrow(SQLException.class) - .when(viewService) - .data(eq(VIEW_1_PRIVILEGED_DTO), any(Instant.class), eq(0L), eq(10L)); - - /* test */ - assertThrows(DatabaseUnavailableException.class, () -> { - viewEndpoint.getData(DATABASE_1_ID, VIEW_1_ID, null, null, null, httpServletRequest, USER_1_PRINCIPAL); - }); - } +// @Test +// @WithMockUser(username = USER_1_USERNAME, authorities = {"view-database-view-data"}) +// public void getData_private_succeeds() throws RemoteUnavailableException, ViewNotFoundException, ViewMalformedException, +// SQLException, DatabaseUnavailableException, QueryMalformedException, PaginationException, +// NotAllowedException, MetadataServiceException { +// +// /* mock */ +// when(metadataServiceGateway.getViewById(DATABASE_1_ID, VIEW_1_ID)) +// .thenReturn(VIEW_1_PRIVILEGED_DTO); +// when(metadataServiceGateway.getAccess(DATABASE_1_ID, USER_1_ID)) +// .thenReturn(DATABASE_1_USER_1_READ_ACCESS_DTO); +// when(httpServletRequest.getMethod()) +// .thenReturn("GET"); +// when(viewService.data(eq(VIEW_1_PRIVILEGED_DTO), any(Instant.class), eq(0L), eq(10L))) +// .thenReturn(VIEW_1_DATA_DTO); +// +// /* test */ +// final ResponseEntity<QueryResultDto> response = viewEndpoint.getData(DATABASE_1_ID, VIEW_1_ID, null, null, null, httpServletRequest, USER_1_PRINCIPAL); +// assertEquals(HttpStatus.OK, response.getStatusCode()); +// assertNotNull(response.getBody()); +// } +// +// @Test +// @WithMockUser(username = USER_1_USERNAME, authorities = {"view-database-view-data"}) +// public void getData_privateHead_succeeds() throws RemoteUnavailableException, ViewNotFoundException, +// ViewMalformedException, SQLException, DatabaseUnavailableException, QueryMalformedException, +// PaginationException, NotAllowedException, MetadataServiceException { +// +// /* mock */ +// when(metadataServiceGateway.getViewById(DATABASE_1_ID, VIEW_3_ID)) +// .thenReturn(VIEW_3_PRIVILEGED_DTO); +// when(metadataServiceGateway.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))) +// .thenReturn(VIEW_3_DATA_COUNT); +// +// /* test */ +// final ResponseEntity<QueryResultDto> response = viewEndpoint.getData(DATABASE_1_ID, VIEW_3_ID, null, null, null, httpServletRequest, USER_1_PRINCIPAL); +// assertEquals(HttpStatus.OK, response.getStatusCode()); +// assertNotNull(response.getHeaders().get("X-Count")); +// assertEquals(1, response.getHeaders().get("X-Count").size()); +// assertEquals(VIEW_3_DATA_COUNT, Long.parseLong(response.getHeaders().get("X-Count").get(0))); +// assertNotNull(response.getHeaders().get("Access-Control-Expose-Headers")); +// assertEquals(1, response.getHeaders().get("Access-Control-Expose-Headers").size()); +// assertEquals("X-Count", response.getHeaders().get("Access-Control-Expose-Headers").get(0)); +// assertNull(response.getBody()); +// } +// +// @Test +// @WithMockUser(username = USER_1_USERNAME, authorities = {"view-database-view-data"}) +// public void getData_unavailable_fails() throws RemoteUnavailableException, ViewNotFoundException, SQLException, +// ViewMalformedException, NotAllowedException, MetadataServiceException { +// +// /* mock */ +// when(metadataServiceGateway.getViewById(DATABASE_1_ID, VIEW_1_ID)) +// .thenReturn(VIEW_1_PRIVILEGED_DTO); +// when(metadataServiceGateway.getAccess(DATABASE_1_ID, USER_1_ID)) +// .thenReturn(DATABASE_1_USER_1_READ_ACCESS_DTO); +// when(httpServletRequest.getMethod()) +// .thenReturn("GET"); +// doThrow(SQLException.class) +// .when(viewService) +// .data(eq(VIEW_1_PRIVILEGED_DTO), any(Instant.class), eq(0L), eq(10L)); +// +// /* test */ +// assertThrows(DatabaseUnavailableException.class, () -> { +// viewEndpoint.getData(DATABASE_1_ID, VIEW_1_ID, null, null, null, httpServletRequest, USER_1_PRINCIPAL); +// }); +// } @Test @WithMockUser(username = USER_1_USERNAME, authorities = {"view-database-view-data"}) @@ -396,7 +395,7 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(NotAllowedException.class, () -> { - viewEndpoint.exportDataset(DATABASE_1_ID, VIEW_3_ID, USER_3_PRINCIPAL); + viewEndpoint.exportDataset(DATABASE_1_ID, VIEW_3_ID, null, USER_3_PRINCIPAL); }); } @@ -412,7 +411,7 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(ViewNotFoundException.class, () -> { - viewEndpoint.exportDataset(DATABASE_1_ID, VIEW_1_ID, USER_4_PRINCIPAL); + viewEndpoint.exportDataset(DATABASE_1_ID, VIEW_1_ID, null, USER_4_PRINCIPAL); }); } @@ -430,7 +429,7 @@ public class ViewEndpointUnitTest extends AbstractUnitTest { /* test */ assertThrows(NotAllowedException.class, () -> { - viewEndpoint.exportDataset(DATABASE_1_ID, VIEW_3_ID, USER_1_PRINCIPAL); + viewEndpoint.exportDataset(DATABASE_1_ID, VIEW_3_ID, null, USER_1_PRINCIPAL); }); } 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 2730e94d9c..939d8b5eac 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 @@ -31,7 +31,8 @@ import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.when; @@ -62,7 +63,7 @@ public class MetadataServiceGatewayUnitTest extends AbstractUnitTest { headers.set("X-Username", CONTAINER_1_PRIVILEGED_USERNAME); headers.set("X-Password", CONTAINER_1_PRIVILEGED_PASSWORD); headers.set("X-Database", DATABASE_1_INTERNALNAME); - headers.set("X-Table", TABLE_1_INTERNALNAME); + headers.set("X-Table", TABLE_1_INTERNAL_NAME); /* mock */ when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(TableDto.class))) @@ -78,7 +79,7 @@ public class MetadataServiceGatewayUnitTest extends AbstractUnitTest { 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()); - assertEquals(TABLE_1_INTERNALNAME, response.getInternalName()); + assertEquals(TABLE_1_INTERNAL_NAME, response.getInternalName()); } @Test @@ -153,7 +154,7 @@ public class MetadataServiceGatewayUnitTest extends AbstractUnitTest { headers.set("X-Username", CONTAINER_1_PRIVILEGED_USERNAME); headers.set("X-Password", CONTAINER_1_PRIVILEGED_PASSWORD); headers.set("X-Database", DATABASE_1_INTERNALNAME); - headers.set("X-Table", TABLE_1_INTERNALNAME); + headers.set("X-Table", TABLE_1_INTERNAL_NAME); /* mock */ when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(TableDto.class))) @@ -167,80 +168,6 @@ public class MetadataServiceGatewayUnitTest extends AbstractUnitTest { }); } - @Test - public void getDatabaseByInternalName_succeeds() throws DatabaseNotFoundException, RemoteUnavailableException, - MetadataServiceException { - - /* mock */ - final HttpHeaders headers = new HttpHeaders(); - headers.set("X-Username", CONTAINER_1_PRIVILEGED_USERNAME); - headers.set("X-Password", CONTAINER_1_PRIVILEGED_PASSWORD); - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(PrivilegedDatabaseDto[].class))) - .thenReturn(ResponseEntity.status(HttpStatus.OK) - .headers(headers) - .body(new PrivilegedDatabaseDto[]{DATABASE_1_PRIVILEGED_DTO})); - - /* test */ - final PrivilegedDatabaseDto response = metadataServiceGateway.getDatabaseByInternalName(DATABASE_1_INTERNALNAME); - assertEquals(response.getId(), response.getId()); - } - - @Test - public void getDatabaseByInternalName_unavailable_fails() { - - /* mock */ - doThrow(HttpServerErrorException.ServiceUnavailable.class) - .when(restTemplate) - .exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(PrivilegedDatabaseDto[].class)); - - /* test */ - assertThrows(RemoteUnavailableException.class, () -> { - metadataServiceGateway.getDatabaseByInternalName(DATABASE_1_INTERNALNAME); - }); - } - - @Test - public void getDatabaseByInternalName_statusCode_fails() { - - /* mock */ - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(PrivilegedDatabaseDto[].class))) - .thenReturn(ResponseEntity.status(HttpStatus.NO_CONTENT) - .body(new PrivilegedDatabaseDto[]{DATABASE_1_PRIVILEGED_DTO})); - - /* test */ - assertThrows(MetadataServiceException.class, () -> { - metadataServiceGateway.getDatabaseByInternalName(DATABASE_1_INTERNALNAME); - }); - } - - @Test - public void getDatabaseByInternalName_emptyBody_fails() { - - /* mock */ - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(PrivilegedDatabaseDto[].class))) - .thenReturn(ResponseEntity.ok() - .build()); - - /* test */ - assertThrows(DatabaseNotFoundException.class, () -> { - metadataServiceGateway.getDatabaseByInternalName(DATABASE_1_INTERNALNAME); - }); - } - - @Test - public void getDatabaseByInternalName_notFound_fails() { - - /* mock */ - when(restTemplate.exchange(anyString(), eq(HttpMethod.GET), eq(HttpEntity.EMPTY), eq(PrivilegedDatabaseDto[].class))) - .thenReturn(ResponseEntity.ok() - .body(new PrivilegedDatabaseDto[]{})); - - /* test */ - assertThrows(DatabaseNotFoundException.class, () -> { - metadataServiceGateway.getDatabaseByInternalName(DATABASE_1_INTERNALNAME); - }); - } - @Test public void getDatabaseById_succeeds() throws RemoteUnavailableException, MetadataServiceException, DatabaseNotFoundException { diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/ActuatorEndpointMvcTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/ActuatorEndpointMvcTest.java index f074abcc87..a7a83a6184 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/ActuatorEndpointMvcTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/ActuatorEndpointMvcTest.java @@ -1,6 +1,5 @@ package at.tuwien.mvc; -import at.tuwien.annotations.MockAmqp; import at.tuwien.test.AbstractUnitTest; import lombok.extern.log4j.Log4j2; import org.junit.jupiter.api.Test; @@ -21,7 +20,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @AutoConfigureMockMvc @SpringBootTest @AutoConfigureObservability -@MockAmqp public class ActuatorEndpointMvcTest extends AbstractUnitTest { @Autowired diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/OpenApiEndpointMvcTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/OpenApiEndpointMvcTest.java index d1426cdf69..b5ed475ea8 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/OpenApiEndpointMvcTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/OpenApiEndpointMvcTest.java @@ -12,7 +12,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.servlet.MockMvc; diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java index f4bd429a90..01bce16b08 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/mvc/PrometheusEndpointMvcTest.java @@ -136,7 +136,7 @@ public class PrometheusEndpointMvcTest extends AbstractUnitTest { /* ignore */ } try { - subsetEndpoint.create(DATABASE_1_ID, ExecuteStatementDto.builder().statement(QUERY_5_STATEMENT).build(), USER_1_PRINCIPAL, 0L, 10L, null); + subsetEndpoint.create(DATABASE_1_ID, ExecuteStatementDto.builder().statement(QUERY_5_STATEMENT).build(), USER_1_PRINCIPAL, httpServletRequest, null, 0L, 10L); } catch (Exception e) { /* ignore */ } 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 94341550a3..6a3851b445 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 @@ -1,6 +1,5 @@ package at.tuwien.mvc; -import at.tuwien.annotations.MockAmqp; import at.tuwien.gateway.MetadataServiceGateway; import at.tuwien.service.SubsetService; import at.tuwien.test.AbstractUnitTest; @@ -26,7 +25,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @AutoConfigureMockMvc @SpringBootTest @AutoConfigureObservability -@MockAmqp public class SubsetEndpointMvcTest extends AbstractUnitTest { @MockBean 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 index 23503384b6..96d78da001 100644 --- 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 @@ -196,8 +196,8 @@ public class SchemaServiceIntegrationTest extends AbstractUnitTest { assertNotNull(unique0.getTable()); assertEquals("some_constraint", unique0.getName()); assertNull(unique0.getTable().getId()); - assertEquals(TABLE_1_INTERNALNAME, unique0.getTable().getName()); - assertEquals(TABLE_1_INTERNALNAME, unique0.getTable().getInternalName()); + 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()); @@ -235,8 +235,8 @@ public class SchemaServiceIntegrationTest extends AbstractUnitTest { final TableBriefDto fk0table = fk0.getTable(); assertNull(fk0table.getId()); assertEquals(DATABASE_1_ID, fk0table.getDatabaseId()); - assertEquals(TABLE_1_INTERNALNAME, fk0table.getName()); - assertEquals(TABLE_1_INTERNALNAME, fk0table.getInternalName()); + assertEquals(TABLE_1_INTERNAL_NAME, fk0table.getName()); + assertEquals(TABLE_1_INTERNAL_NAME, fk0table.getInternalName()); assertNotNull(fk0.getOnDelete()); assertNotNull(fk0.getOnUpdate()); assertNotNull(fk0.getReferencedTable()); 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 0f0820926f..215e919844 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 @@ -1,19 +1,13 @@ package at.tuwien.service; -import at.tuwien.ExportResourceDto; import at.tuwien.api.database.query.QueryDto; -import at.tuwien.api.database.query.QueryResultDto; import at.tuwien.api.identifier.IdentifierDto; import at.tuwien.config.MariaDbConfig; import at.tuwien.config.MariaDbContainerConfig; -import at.tuwien.config.S3Config; import at.tuwien.exception.*; -import at.tuwien.gateway.AnalyseServiceGateway; import at.tuwien.gateway.MetadataServiceGateway; import at.tuwien.test.AbstractUnitTest; import lombok.extern.log4j.Log4j2; -import org.apache.commons.lang3.RandomStringUtils; -import org.apache.spark.sql.Dataset; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -25,16 +19,11 @@ import org.testcontainers.containers.MariaDBContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; -import java.io.IOException; -import java.math.BigInteger; import java.sql.SQLException; -import java.time.Instant; import java.util.List; import java.util.Map; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.when; @Log4j2 @@ -49,18 +38,9 @@ public class SubsetServiceIntegrationTest extends AbstractUnitTest { @MockBean private MetadataServiceGateway metadataServiceGateway; - @MockBean - private AnalyseServiceGateway dataDatabaseSidecarGateway; - - @MockBean - private StorageService storageService; - @Container private static MariaDBContainer<?> mariaDBContainer = MariaDbContainerConfig.getContainer(); - @Autowired - private S3Config s3Config; - @BeforeEach public void beforeEach() throws SQLException { genesis(); @@ -69,138 +49,6 @@ public class SubsetServiceIntegrationTest extends AbstractUnitTest { MariaDbConfig.createInitDatabase(CONTAINER_1_PRIVILEGED_DTO, DATABASE_1_DTO); } - @Test - public void execute_succeeds() throws QueryStoreInsertException, TableMalformedException, SQLException, - QueryNotFoundException, UserNotFoundException, NotAllowedException, RemoteUnavailableException, - MetadataServiceException, DatabaseNotFoundException, InterruptedException { - - /* pre-condition */ - Thread.sleep(1000) /* wait for test container some more */; - - /* mock */ - when(metadataServiceGateway.getUserById(QUERY_1_CREATED_BY)) - .thenReturn(QUERY_1_CREATOR); - - /* test */ - final QueryResultDto response = queryService.execute(DATABASE_1_PRIVILEGED_DTO, QUERY_1_STATEMENT, Instant.now(), USER_1_ID, 0L, 10L, null, null); - assertNotNull(response); - assertNotNull(response.getId()); - assertNotNull(response.getHeaders()); - assertEquals(5, response.getHeaders().size()); - assertEquals(List.of(Map.of("id", 0), Map.of("date", 1), Map.of("location", 2), Map.of("mintemp", 3), Map.of("rainfall", 4)), response.getHeaders()); - assertNotNull(response.getResult()); - assertEquals(3, response.getResult().size()); - /* row 0 */ - assertEquals(BigInteger.valueOf(1L), response.getResult().get(0).get("id")); - assertEquals(Instant.ofEpochSecond(1228089600), response.getResult().get(0).get("date")); - assertEquals("Albury", response.getResult().get(0).get("location")); - assertEquals(13.4, response.getResult().get(0).get("mintemp")); - assertEquals(0.6, response.getResult().get(0).get("rainfall")); - /* row 1 */ - assertEquals(BigInteger.valueOf(2L), response.getResult().get(1).get("id")); - assertEquals(Instant.ofEpochSecond(1228176000), response.getResult().get(1).get("date")); - assertEquals("Albury", response.getResult().get(1).get("location")); - assertEquals(7.4, response.getResult().get(1).get("mintemp")); - assertEquals(0.0, response.getResult().get(1).get("rainfall")); - /* row 2 */ - assertEquals(BigInteger.valueOf(3L), response.getResult().get(2).get("id")); - assertEquals(Instant.ofEpochSecond(1228262400), response.getResult().get(2).get("date")); - assertEquals("Albury", response.getResult().get(2).get("location")); - assertEquals(12.9, response.getResult().get(2).get("mintemp")); - assertEquals(0.0, response.getResult().get(2).get("rainfall")); - } - - @Test - public void execute_joinWithAlias_succeeds() throws QueryStoreInsertException, TableMalformedException, - SQLException, QueryNotFoundException, UserNotFoundException, NotAllowedException, - RemoteUnavailableException, MetadataServiceException, DatabaseNotFoundException, InterruptedException { - - /* pre-condition */ - Thread.sleep(1000) /* wait for test container some more */; - - /* mock */ - when(metadataServiceGateway.getUserById(QUERY_1_CREATED_BY)) - .thenReturn(QUERY_1_CREATOR); - - /* test */ - final QueryResultDto response = queryService.execute(DATABASE_1_PRIVILEGED_DTO, QUERY_7_STATEMENT, Instant.now(), USER_1_ID, 0L, 10L, null, null); - assertNotNull(response); - assertNotNull(response.getId()); - assertNotNull(response.getHeaders()); - assertEquals(5, response.getHeaders().size()); - assertEquals(List.of(Map.of("id", 0), Map.of("date", 1), Map.of("location", 2), Map.of("lat", 3), Map.of("lng", 4)), response.getHeaders()); - assertNotNull(response.getResult()); - assertEquals(1, response.getResult().size()); - /* row 0 */ - assertEquals(BigInteger.valueOf(1L), response.getResult().get(0).get("id")); - assertEquals(Instant.ofEpochSecond(1228089600), response.getResult().get(0).get("date")); - assertEquals("Albury", response.getResult().get(0).get("location")); - assertEquals(-36.0653583, response.getResult().get(0).get("lat")); - assertEquals(146.9112214, response.getResult().get(0).get("lng")); - } - - @Test - public void execute_oneResult_succeeds() throws QueryStoreInsertException, TableMalformedException, SQLException, - QueryNotFoundException, UserNotFoundException, NotAllowedException, RemoteUnavailableException, - MetadataServiceException, DatabaseNotFoundException, InterruptedException { - - /* pre-condition */ - Thread.sleep(1000) /* wait for test container some more */; - - /* mock */ - when(metadataServiceGateway.getIdentifiers(DATABASE_1_ID, QUERY_1_ID)) - .thenReturn(List.of(IDENTIFIER_2_DTO)); - when(metadataServiceGateway.getUserById(QUERY_1_CREATED_BY)) - .thenReturn(QUERY_1_CREATOR); - - /* test */ - final QueryResultDto response = queryService.execute(DATABASE_1_PRIVILEGED_DTO, QUERY_1_STATEMENT, Instant.now(), USER_1_ID, 0L, 1L, null, null); - assertNotNull(response); - assertNotNull(response.getId()); - assertNotNull(response.getHeaders()); - assertEquals(5, response.getHeaders().size()); - assertEquals(List.of(Map.of("id", 0), Map.of("date", 1), Map.of("location", 2), Map.of("mintemp", 3), Map.of("rainfall", 4)), response.getHeaders()); - assertNotNull(response.getResult()); - assertEquals(1, response.getResult().size()); - /* row 0 */ - assertEquals(BigInteger.valueOf(1L), response.getResult().get(0).get("id")); - assertEquals(Instant.ofEpochSecond(1228089600), response.getResult().get(0).get("date")); - assertEquals("Albury", response.getResult().get(0).get("location")); - assertEquals(13.4, response.getResult().get(0).get("mintemp")); - assertEquals(0.6, response.getResult().get(0).get("rainfall")); - } - - @Test - public void execute_oneResultPagination_succeeds() throws QueryStoreInsertException, TableMalformedException, - SQLException, QueryNotFoundException, UserNotFoundException, NotAllowedException, - RemoteUnavailableException, MetadataServiceException, DatabaseNotFoundException, InterruptedException { - - /* pre-condition */ - Thread.sleep(1000) /* wait for test container some more */; - - /* mock */ - when(metadataServiceGateway.getUserById(USER_1_ID)) - .thenReturn(USER_1_DTO); - when(metadataServiceGateway.getIdentifiers(eq(DATABASE_1_ID), anyLong())) - .thenReturn(List.of()); - - /* test */ - final QueryResultDto response = queryService.execute(DATABASE_1_PRIVILEGED_DTO, QUERY_1_STATEMENT, Instant.now(), USER_1_ID, 1L, 1L, null, null); - assertNotNull(response); - assertNotNull(response.getId()); - assertNotNull(response.getHeaders()); - assertEquals(5, response.getHeaders().size()); - assertEquals(List.of(Map.of("id", 0), Map.of("date", 1), Map.of("location", 2), Map.of("mintemp", 3), Map.of("rainfall", 4)), response.getHeaders()); - assertNotNull(response.getResult()); - assertEquals(1, response.getResult().size()); - /* row 1 */ - assertEquals(BigInteger.valueOf(2L), response.getResult().get(0).get("id")); - assertEquals(Instant.ofEpochSecond(1228176000), response.getResult().get(0).get("date")); - assertEquals("Albury", response.getResult().get(0).get("location")); - assertEquals(7.4, response.getResult().get(0).get("mintemp")); - assertEquals(0.0, response.getResult().get(0).get("rainfall")); - } - @Test public void findAll_succeeds() throws SQLException, QueryNotFoundException, RemoteUnavailableException, MetadataServiceException, DatabaseNotFoundException, InterruptedException { @@ -300,18 +148,6 @@ public class SubsetServiceIntegrationTest extends AbstractUnitTest { }); } - @Test - public void export_succeeds() throws SQLException, StorageUnavailableException, QueryMalformedException, - RemoteUnavailableException, IOException, StorageNotFoundException, InterruptedException, - AnalyseServiceException, ViewNotFoundException, MalformedException { - - /* mock */ - MariaDbConfig.dropQueryStore(DATABASE_1_PRIVILEGED_DTO); - - /* test */ - export_generic(); - } - protected void findById_generic(Long queryId) throws RemoteUnavailableException, SQLException, UserNotFoundException, QueryNotFoundException, MetadataServiceException, DatabaseNotFoundException, InterruptedException { @@ -378,26 +214,4 @@ public class SubsetServiceIntegrationTest extends AbstractUnitTest { assertEquals(0, response.size()); } - protected void export_generic() throws StorageUnavailableException, SQLException, RemoteUnavailableException, - QueryMalformedException, StorageNotFoundException, IOException, InterruptedException, - AnalyseServiceException, ViewNotFoundException, MalformedException { - final String filename = RandomStringUtils.randomAlphanumeric(40).toLowerCase() + ".tmp"; - EXPORT_RESOURCE_DTO.setFilename(filename); - - /* pre-condition */ - Thread.sleep(1000) /* wait for test container some more */; - - /* mock */ - doNothing() - .when(dataDatabaseSidecarGateway) - .exportTable(anyLong(), anyLong()); - when(storageService.transformDataset(any(Dataset.class))) - .thenReturn(EXPORT_RESOURCE_DTO); - - /* test */ - final ExportResourceDto response = queryService.export(DATABASE_1_PRIVILEGED_DTO, QUERY_1_DTO, Instant.now()); - assertEquals(filename, response.getFilename()); - assertNotNull(response.getResource().getInputStream()); - } - } 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 3b59dac2c1..1236c549d7 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 @@ -1,7 +1,5 @@ package at.tuwien.service; -import at.tuwien.ExportResourceDto; -import at.tuwien.api.database.query.QueryResultDto; import at.tuwien.api.database.table.*; import at.tuwien.api.database.table.columns.ColumnCreateDto; import at.tuwien.api.database.table.columns.ColumnDto; @@ -18,7 +16,6 @@ import at.tuwien.api.database.table.internal.TableCreateDto; import at.tuwien.config.MariaDbConfig; import at.tuwien.config.MariaDbContainerConfig; import at.tuwien.exception.*; -import at.tuwien.gateway.AnalyseServiceGateway; import at.tuwien.gateway.MetadataServiceGateway; import at.tuwien.test.AbstractUnitTest; import lombok.extern.log4j.Log4j2; @@ -27,23 +24,18 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.core.io.InputStreamResource; 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.io.InputStream; import java.math.BigDecimal; -import java.math.BigInteger; import java.sql.SQLException; import java.time.Instant; import java.util.*; import static at.tuwien.service.SchemaServiceIntegrationTest.assertColumn; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.when; @Log4j2 @@ -58,9 +50,6 @@ public class TableServiceIntegrationTest extends AbstractUnitTest { @MockBean private MetadataServiceGateway metadataServiceGateway; - @MockBean - private AnalyseServiceGateway dataDatabaseSidecarGateway; - @MockBean private StorageService storageService; @@ -618,12 +607,12 @@ public class TableServiceIntegrationTest extends AbstractUnitTest { TableExistsException { /* mock */ - MariaDbConfig.dropTable(DATABASE_1_PRIVILEGED_DTO, TABLE_1_INTERNALNAME); + 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_INTERNALNAME, response.getInternalName()); + assertEquals(TABLE_1_INTERNAL_NAME, response.getInternalName()); assertEquals(TABLE_1_COLUMNS.size(), response.getColumns().size()); } @@ -674,49 +663,6 @@ public class TableServiceIntegrationTest extends AbstractUnitTest { }); } - @Test - public void getPaginatedData_succeeds() throws SQLException, TableMalformedException { - - /* test */ - final QueryResultDto response = tableService.getPaginatedData(TABLE_1_PRIVILEGED_DTO, null, 0L, 10L); - assertEquals(TABLE_1_ID, response.getId()); - final List<Map<String, Integer>> headers = response.getHeaders(); - assertEquals(5, headers.size()); - assertEquals(0, headers.get(0).get("id")); - assertEquals(1, headers.get(1).get("date")); - assertEquals(2, headers.get(2).get("location")); - assertEquals(3, headers.get(3).get("mintemp")); - assertEquals(4, headers.get(4).get("rainfall")); - final List<Map<String, Object>> result = response.getResult(); - assertEquals(Instant.ofEpochSecond(1228089600), result.get(0).get("date")); - assertEquals(0.6, result.get(0).get("rainfall")); - assertEquals("Albury", result.get(0).get("location")); - assertEquals(BigInteger.valueOf(1L), result.get(0).get("id")); - assertEquals(13.4, result.get(0).get("mintemp")); - assertEquals(Instant.ofEpochSecond(1228176000), result.get(1).get("date")); - assertEquals(0.0, result.get(1).get("rainfall")); - assertEquals("Albury", result.get(1).get("location")); - assertEquals(BigInteger.valueOf(2L), result.get(1).get("id")); - assertEquals(7.4, result.get(1).get("mintemp")); - assertEquals(Instant.ofEpochSecond(1228262400), result.get(2).get("date")); - assertEquals(0.0, result.get(2).get("rainfall")); - assertEquals("Albury", result.get(2).get("location")); - assertEquals(BigInteger.valueOf(3L), result.get(2).get("id")); - assertEquals(12.9, result.get(2).get("mintemp")); - } - - @Test - public void getPaginatedData_notFound_fails() throws SQLException { - - /* mock */ - MariaDbConfig.createDatabase(CONTAINER_1_PRIVILEGED_DTO, DATABASE_2_INTERNALNAME); - - /* test */ - assertThrows(TableMalformedException.class, () -> { - tableService.getPaginatedData(TABLE_5_PRIVILEGED_DTO, null, 0L, 10L); - }); - } - @Test public void history_succeeds() throws SQLException, TableNotFoundException { @@ -741,36 +687,4 @@ public class TableServiceIntegrationTest extends AbstractUnitTest { }); } - @Test - public void exportTable_succeeds() throws QueryMalformedException, RemoteUnavailableException, - StorageNotFoundException, StorageUnavailableException, AnalyseServiceException, TableNotFoundException, - MalformedException { - final ExportResourceDto mock = ExportResourceDto.builder() - .filename("weather_aus.csv") - .resource(new InputStreamResource(InputStream.nullInputStream())) - .build(); - - /* mock */ - doNothing() - .when(dataDatabaseSidecarGateway) - .exportTable(anyLong(), anyLong()); - when(storageService.getResource("weather_aus.csv")) - .thenReturn(mock); - - /* test */ - final ExportResourceDto response = tableService.exportDataset(TABLE_1_PRIVILEGED_DTO, null); - } - - @Test - public void exportDataset_malformedData_fails() throws SQLException { - - /* mock */ - MariaDbConfig.createDatabase(CONTAINER_1_PRIVILEGED_DTO, DATABASE_2_INTERNALNAME); - - /* test */ - assertThrows(QueryMalformedException.class, () -> { - tableService.exportDataset(TABLE_5_PRIVILEGED_DTO, null); - }); - } - } 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 4018c95334..f3b309cba4 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 @@ -2,7 +2,6 @@ package at.tuwien.service; import at.tuwien.api.database.ViewColumnDto; import at.tuwien.api.database.ViewDto; -import at.tuwien.api.database.query.QueryResultDto; import at.tuwien.config.MariaDbConfig; import at.tuwien.config.MariaDbContainerConfig; import at.tuwien.exception.DatabaseMalformedException; @@ -21,9 +20,7 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import java.sql.SQLException; -import java.time.Instant; import java.util.List; -import java.util.Map; import static org.junit.jupiter.api.Assertions.*; @@ -51,7 +48,7 @@ public class ViewServiceIntegrationTest extends AbstractUnitTest { public void delete_succeeds() throws SQLException, ViewMalformedException { /* test */ - viewService.delete(VIEW_1_PRIVILEGED_DTO); + viewService.delete(DATABASE_1_PRIVILEGED_DTO, VIEW_1_INTERNAL_NAME); } @Test @@ -75,36 +72,6 @@ public class ViewServiceIntegrationTest extends AbstractUnitTest { } - @Test - public void data_succeeds() throws SQLException, ViewMalformedException { - - /* test */ - final QueryResultDto response = viewService.data(VIEW_2_PRIVILEGED_DTO, Instant.now(), 0L, 10L); - assertNotNull(response); - assertNotNull(response.getId()); - assertEquals(VIEW_2_ID, response.getId()); - assertNotNull(response.getHeaders()); - assertEquals(4, response.getHeaders().size()); - assertEquals(List.of(Map.of("date", 0), Map.of("loc", 1), Map.of("mintemp", 2), Map.of("rainfall", 3)), response.getHeaders()); - assertNotNull(response.getResult()); - assertEquals(3, response.getResult().size()); - /* row 0 */ - assertEquals(Instant.ofEpochSecond(1228089600), response.getResult().get(0).get("date")); - assertEquals("Albury", response.getResult().get(0).get("loc")); - assertEquals(13.4, response.getResult().get(0).get("mintemp")); - assertEquals(0.6, response.getResult().get(0).get("rainfall")); - /* row 1 */ - assertEquals(Instant.ofEpochSecond(1228176000), response.getResult().get(1).get("date")); - assertEquals("Albury", response.getResult().get(1).get("loc")); - assertEquals(7.4, response.getResult().get(1).get("mintemp")); - assertEquals(0.0, response.getResult().get(1).get("rainfall")); - /* row 2 */ - assertEquals(Instant.ofEpochSecond(1228262400), response.getResult().get(2).get("date")); - assertEquals("Albury", response.getResult().get(2).get("loc")); - assertEquals(12.9, response.getResult().get(2).get("mintemp")); - assertEquals(0.0, response.getResult().get(2).get("rainfall")); - } - @Test public void getSchemas_succeeds() throws SQLException, ViewNotFoundException, DatabaseMalformedException { diff --git a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/validation/EndpointValidatorUnitTest.java b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/validation/EndpointValidatorUnitTest.java index b12313dfe9..e237c32d28 100644 --- a/dbrepo-data-service/rest-service/src/test/java/at/tuwien/validation/EndpointValidatorUnitTest.java +++ b/dbrepo-data-service/rest-service/src/test/java/at/tuwien/validation/EndpointValidatorUnitTest.java @@ -1,6 +1,5 @@ package at.tuwien.validation; -import at.tuwien.annotations.MockAmqp; import at.tuwien.exception.PaginationException; import at.tuwien.exception.QueryNotSupportedException; import at.tuwien.test.AbstractUnitTest; @@ -12,19 +11,14 @@ import org.springframework.boot.test.autoconfigure.actuate.observability.AutoCon import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.web.servlet.MockMvc; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @Log4j2 @ExtendWith(SpringExtension.class) @AutoConfigureMockMvc @SpringBootTest @AutoConfigureObservability -@MockAmqp public class EndpointValidatorUnitTest extends AbstractUnitTest { @Autowired diff --git a/dbrepo-data-service/rest-service/src/test/resources/init/weather.sql b/dbrepo-data-service/rest-service/src/test/resources/init/weather.sql index 322e67cc07..7b8dd082b5 100644 --- a/dbrepo-data-service/rest-service/src/test/resources/init/weather.sql +++ b/dbrepo-data-service/rest-service/src/test/resources/init/weather.sql @@ -1,6 +1,8 @@ /* https://www.kaggle.com/jsphyg/weather-dataset-rattle-package */ -CREATE DATABASE weather; -USE weather; +CREATE +DATABASE weather; +USE +weather; CREATE TABLE weather_location ( @@ -12,8 +14,8 @@ CREATE TABLE weather_location CREATE TABLE weather_aus ( id SERIAL PRIMARY KEY, - `date` DATE NOT NULL, - location VARCHAR(255) NULL COMMENT 'Closest city', + `date` DATE NOT NULL, + location VARCHAR(255) NULL COMMENT 'Closest city', mintemp DOUBLE PRECISION NULL, rainfall DOUBLE PRECISION NULL, FOREIGN KEY (location) REFERENCES weather_location (location) ON DELETE SET NULL, @@ -44,7 +46,7 @@ CREATE TABLE sensor CREATE TABLE exotic_boolean ( - `bool_default` BOOLEAN NOT NULL PRIMARY KEY, + `bool_default` BOOLEAN NOT NULL PRIMARY KEY, `bool_tinyint` TINYINT(1) NOT NULL, `bool_tinyint_unsigned` TINYINT(1) UNSIGNED NOT NULL ) WITH SYSTEM VERSIONING; @@ -91,4 +93,11 @@ CREATE VIEW not_in_metadata_db2 AS ( select `date`, `location`, `mintemp` as `MinTemp`, `rainfall` as `Rainfall` from `weather_aus` +where `location` = 'Vienna'); + +-- internal view who should not be indexed into schema +CREATE VIEW 5c7ba02f681b889892ee82987aa6c74ce45a30973cfef06b78ce797f25189b9a AS +( +select `date`, `location`, `mintemp` as `MinTemp`, `rainfall` as `Rainfall` +from `weather_aus` where `location` = 'Vienna'); \ No newline at end of file diff --git a/dbrepo-data-service/services/pom.xml b/dbrepo-data-service/services/pom.xml index ee3c9b2b6c..66f014ef7c 100644 --- a/dbrepo-data-service/services/pom.xml +++ b/dbrepo-data-service/services/pom.xml @@ -6,18 +6,18 @@ <parent> <groupId>at.tuwien</groupId> <artifactId>dbrepo-data-service</artifactId> - <version>1.5.2</version> + <version>1.5.3</version> </parent> <artifactId>services</artifactId> <name>dbrepo-data-service-services</name> - <version>1.5.2</version> + <version>1.5.3</version> <dependencies> <dependency> <groupId>at.tuwien</groupId> <artifactId>dbrepo-data-service-querystore</artifactId> - <version>1.5.2</version> + <version>1.5.3</version> </dependency> </dependencies> 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 282e7d593f..6575913040 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 @@ -4,11 +4,9 @@ import at.tuwien.api.container.internal.PrivilegedContainerDto; 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.TableStatisticDto; import at.tuwien.api.database.table.internal.PrivilegedTableDto; import at.tuwien.api.identifier.IdentifierDto; import at.tuwien.api.user.PrivilegedUserDto; -import at.tuwien.api.user.UserBriefDto; import at.tuwien.api.user.UserDto; import at.tuwien.exception.*; import jakarta.validation.constraints.NotNull; @@ -42,18 +40,6 @@ public interface MetadataServiceGateway { PrivilegedDatabaseDto getDatabaseById(Long id) throws DatabaseNotFoundException, RemoteUnavailableException, MetadataServiceException; - /** - * Get a database with given internal name from the metadata service. - * - * @param internalName The internal name. - * @return The database, if successful. - * @throws DatabaseNotFoundException The database was not found in the metadata service. - * @throws RemoteUnavailableException The remote service is not available. - * @throws MetadataServiceException The remote service returned invalid data. - */ - PrivilegedDatabaseDto getDatabaseByInternalName(String internalName) throws DatabaseNotFoundException, - RemoteUnavailableException, MetadataServiceException; - /** * Get a table with given database id and table id from the metadata service. * @@ -91,17 +77,6 @@ public interface MetadataServiceGateway { */ UserDto getUserById(UUID userId) throws RemoteUnavailableException, UserNotFoundException, MetadataServiceException; - /** - * Get a user with given username from the metadata service. - * - * @return The user, if successful. Otherwise empty list. - * @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. - */ - UUID getSystemUserId() throws RemoteUnavailableException, UserNotFoundException, - MetadataServiceException; - /** * Get a user with given user 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 3c03f022b3..d83a06389d 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 @@ -10,9 +10,7 @@ import at.tuwien.api.database.table.TableDto; import at.tuwien.api.database.table.internal.PrivilegedTableDto; import at.tuwien.api.identifier.IdentifierDto; import at.tuwien.api.user.PrivilegedUserDto; -import at.tuwien.api.user.UserBriefDto; import at.tuwien.api.user.UserDto; -import at.tuwien.config.GatewayConfig; import at.tuwien.exception.*; import at.tuwien.gateway.MetadataServiceGateway; import at.tuwien.mapper.MetadataMapper; @@ -37,14 +35,11 @@ import java.util.UUID; public class MetadataServiceGatewayImpl implements MetadataServiceGateway { private final RestTemplate restTemplate; - private final GatewayConfig gatewayConfig; private final MetadataMapper metadataMapper; @Autowired - public MetadataServiceGatewayImpl(RestTemplate restTemplate, GatewayConfig gatewayConfig, - MetadataMapper metadataMapper) { + public MetadataServiceGatewayImpl(RestTemplate restTemplate, MetadataMapper metadataMapper) { this.restTemplate = restTemplate; - this.gatewayConfig = gatewayConfig; this.metadataMapper = metadataMapper; } @@ -121,37 +116,6 @@ public class MetadataServiceGatewayImpl implements MetadataServiceGateway { return database; } - @Override - public PrivilegedDatabaseDto getDatabaseByInternalName(String internalName) throws DatabaseNotFoundException, - RemoteUnavailableException, MetadataServiceException { - final ResponseEntity<PrivilegedDatabaseDto[]> response; - final String url = "/api/database/"; - log.debug("find privileged database from url: {}", url); - try { - response = restTemplate.exchange(url, HttpMethod.GET, HttpEntity.EMPTY, PrivilegedDatabaseDto[].class); - } catch (ResourceAccessException | HttpServerErrorException e) { - log.error("Failed to find database with internal name {}: {}", internalName, e.getMessage()); - throw new RemoteUnavailableException("Failed to find database: " + e.getMessage(), e); - } - if (!response.getStatusCode().equals(HttpStatus.OK)) { - log.error("Failed to find database with internal name {}: service responded unsuccessful: {}", internalName, response.getStatusCode()); - throw new MetadataServiceException("Failed to find database: service responded unsuccessful: " + response.getStatusCode()); - } - /* body first, then headers next */ - if (response.getBody() == null || response.getBody().length != 1) { - log.error("Failed to find database with internal name {}: body is empty", internalName); - throw new DatabaseNotFoundException("Failed to find database: body is empty"); - } - final List<String> expectedHeaders = List.of("X-Username", "X-Password"); - if (!response.getHeaders().keySet().containsAll(expectedHeaders)) { - log.error("Failed to find all privileged database headers"); - log.debug("expected headers: {}", expectedHeaders); - log.debug("found headers: {}", response.getHeaders().keySet()); - throw new MetadataServiceException("Failed to find all privileged database headers"); - } - return response.getBody()[0]; - } - @Override public PrivilegedTableDto getTableById(Long databaseId, Long id) throws TableNotFoundException, RemoteUnavailableException, MetadataServiceException { @@ -259,34 +223,6 @@ public class MetadataServiceGatewayImpl implements MetadataServiceGateway { return response.getBody(); } - @Override - public UUID getSystemUserId() throws RemoteUnavailableException, UserNotFoundException, - MetadataServiceException { - final ResponseEntity<UserBriefDto[]> response; - try { - response = restTemplate.exchange("/api/user?username=" + gatewayConfig.getSystemUsername(), HttpMethod.GET, HttpEntity.EMPTY, UserBriefDto[].class); - } catch (ResourceAccessException | HttpServerErrorException e) { - log.error("Failed to find user with username {}: {}", gatewayConfig.getSystemUsername(), e.getMessage()); - throw new RemoteUnavailableException("Failed to find user with username " + gatewayConfig.getSystemUsername() + ": " + e.getMessage(), e); - } catch (HttpClientErrorException.NotFound e) { - log.error("Failed to find user with username {}: not found: {}", gatewayConfig.getSystemUsername(), e.getMessage()); - throw new UserNotFoundException("Failed to find user with username " + gatewayConfig.getSystemUsername() + ": " + e.getMessage(), e); - } - if (!response.getStatusCode().equals(HttpStatus.OK)) { - log.error("Failed to find user with username {}: service responded unsuccessful: {}", gatewayConfig.getSystemUsername(), response.getStatusCode()); - throw new MetadataServiceException("Failed to find user with username " + gatewayConfig.getSystemUsername() + ": service responded unsuccessful: " + response.getStatusCode()); - } - if (response.getBody() == null) { - log.error("Failed to find user with username {}: body is empty", gatewayConfig.getSystemUsername()); - throw new MetadataServiceException("Failed to find user with username " + gatewayConfig.getSystemUsername() + ": body is empty"); - } - if (response.getBody().length != 1) { - log.error("Failed to find system user: expected exactly one result but got {}", response.getBody().length); - throw new MetadataServiceException("Failed to find system user: expected exactly one result but got " + response.getBody().length); - } - return response.getBody()[0].getId(); - } - @Override public PrivilegedUserDto getPrivilegedUserById(UUID userId) throws RemoteUnavailableException, UserNotFoundException, MetadataServiceException { diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/DataMapper.java b/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/DataMapper.java index 7a268f5248..ccb49288c5 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/DataMapper.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/DataMapper.java @@ -4,7 +4,6 @@ import at.tuwien.api.database.DatabaseDto; import at.tuwien.api.database.ViewColumnDto; import at.tuwien.api.database.ViewDto; import at.tuwien.api.database.query.QueryDto; -import at.tuwien.api.database.query.QueryResultDto; import at.tuwien.api.database.table.TableBriefDto; import at.tuwien.api.database.table.TableDto; import at.tuwien.api.database.table.TableHistoryDto; @@ -24,13 +23,7 @@ import at.tuwien.api.user.UserDto; import at.tuwien.config.QueryConfig; import at.tuwien.exception.QueryNotFoundException; import at.tuwien.exception.TableNotFoundException; -import net.sf.jsqlparser.JSQLParserException; -import net.sf.jsqlparser.parser.CCJSqlParserManager; -import net.sf.jsqlparser.schema.Column; -import net.sf.jsqlparser.schema.Table; -import net.sf.jsqlparser.statement.select.*; import org.apache.hadoop.shaded.com.google.common.hash.Hashing; -import org.apache.hadoop.shaded.org.apache.commons.codec.binary.Hex; import org.apache.hadoop.shaded.org.apache.commons.io.FileUtils; import org.jetbrains.annotations.NotNull; import org.mapstruct.Mapper; @@ -40,15 +33,11 @@ import org.mapstruct.Mappings; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.io.StringReader; -import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.sql.*; -import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeFormatterBuilder; import java.util.*; import java.util.stream.Collectors; @@ -210,161 +199,6 @@ public interface DataMapper { return view; } - /** - * Parse columns from a SQL statement of a known database. - * - * @param databaseId The database id. - * @param tables The list of tables. - * @param query The SQL statement. - * @return The list of columns. - * @throws JSQLParserException The table/view or column was not found in the database. - */ - default List<ColumnDto> parseColumns(Long databaseId, List<TableDto> tables, String query) throws JSQLParserException { - final List<ColumnDto> columns = new ArrayList<>(); - final CCJSqlParserManager parserRealSql = new CCJSqlParserManager(); - final net.sf.jsqlparser.statement.Statement statement = parserRealSql.parse(new StringReader(query)); - log.trace("parse columns from query: {}", query); - /* bi-directional mapping */ - tables.forEach(table -> table.getColumns() - .forEach(column -> column.setTable(table))); - /* check */ - if (!(statement instanceof Select selectStatement)) { - log.error("Query attempts to update the dataset, not a SELECT statement"); - throw new JSQLParserException("Query attempts to update the dataset"); - } - /* start parsing */ - final PlainSelect ps = (PlainSelect) selectStatement.getSelectBody(); - final List<SelectItem> clauses = ps.getSelectItems(); - log.trace("columns referenced in the from-clause: {}", clauses); - /* Parse all tables */ - final List<FromItem> fromItems = new ArrayList<>(fromItemToFromItems(ps.getFromItem())); - if (ps.getJoins() != null && !ps.getJoins().isEmpty()) { - log.trace("query contains join items: {}", ps.getJoins()); - for (net.sf.jsqlparser.statement.select.Join j : ps.getJoins()) { - if (j.getRightItem() != null) { - fromItems.add(j.getRightItem()); - } - } - } - final List<ColumnDto> allColumns = tables.stream() - .map(TableDto::getColumns) - .flatMap(List::stream) - .toList(); - log.trace("columns referenced in the from-clause and join-clause(s): {}", clauses); - /* Checking if all columns exist */ - for (SelectItem clause : clauses) { - final SelectExpressionItem item = (SelectExpressionItem) clause; - final Column column = (Column) item.getExpression(); - final String columnName = column.getColumnName().replace("`", ""); - final List<ColumnDto> filteredColumns = allColumns.stream() - .filter(c -> (c.getAlias() != null && c.getAlias().equals(columnName)) || c.getInternalName().equals(columnName)) - .toList(); - String tableOrView = null; - for (Table t : fromItems.stream().map(t -> (net.sf.jsqlparser.schema.Table) t).toList()) { - if (column.getTable() == null) { - /* column does not reference a specific table, find out */ - final List<String> filteredTables = filteredColumns.stream() - .map(c -> c.getTable().getInternalName()) - .filter(table -> fromItems.stream().map(f -> (Table) f).anyMatch(otherTable -> otherTable.getName().replace("`", "").equals(table))) - .toList(); - if (filteredTables.size() != 1) { - log.error("Failed to filter column {} to exactly one match: {}", columnName, filteredTables.stream().map(table -> table + "." + columnName).toList()); - throw new JSQLParserException("Failed to filter column " + columnName + " to exactly one match"); - } - if (tableMatches(t, filteredTables.get(0))) { - tableOrView = t.getName().replace("`", ""); - break; - } - } - /* column references a specific table */ - final String tableOrAlias = (t.getAlias() != null ? t.getAlias().getName() : column.getTable().getName()) - .replace("`", ""); - if (tableMatches(t, tableOrAlias)) { - tableOrView = t.getName().replace("`", ""); - break; - } - } - if (tableOrView == null) { - log.error("Failed to find table/view {} (with designator {})", column.getTable().getName(), column.getTable().getAlias()); - throw new JSQLParserException("Failed to find table/view " + column.getTable().getName() + " (with alias " + column.getTable().getAlias() + ")"); - } - final String finalTableOrView = tableOrView; - final List<ColumnDto> selectColumns = filteredColumns.stream() - .filter(c -> c.getTable().getInternalName().equals(finalTableOrView)) - .toList(); - final ColumnDto resultColumn; - if (selectColumns.size() != 1) { - if (filteredColumns.size() != 1) { - log.error("Failed to filter column {} to exactly one match: {}", columnName, selectColumns.stream().map(c -> c.getTable().getInternalName() + "." + c.getInternalName()).toList()); - throw new JSQLParserException("Failed to filter column " + columnName + " to exactly one match"); - } - resultColumn = filteredColumns.get(0); - } else { - resultColumn = selectColumns.get(0); - } - if (item.getAlias() != null) { - resultColumn.setAlias(item.getAlias().getName().replace("`", "")); - } - resultColumn.setDatabaseId(databaseId); - resultColumn.setTable(resultColumn.getTable()); - resultColumn.setTableId(resultColumn.getTable().getId()); - log.trace("found column with internal name {} and alias {}", resultColumn.getInternalName(), resultColumn.getAlias()); - columns.add(resultColumn); - } - return columns; - } - - default boolean tableMatches(net.sf.jsqlparser.schema.Table table, String tableOrDesignator) { - final String tableName = table.getName() - .trim() - .replace("`", ""); - if (table.getAlias() == null) { - /* table does not have designator */ - log.trace("table '{}' has no designator", tableName); - return tableName.equals(tableOrDesignator); - } - /* has designator */ - final String designator = table.getAlias() - .getName() - .trim() - .replace("`", ""); - log.trace("table '{}' has designator {}", tableName, designator); - return designator.equals(tableOrDesignator); - } - - default List<FromItem> fromItemToFromItems(FromItem data) throws JSQLParserException { - return fromItemToFromItems(data, 0); - } - - default List<FromItem> fromItemToFromItems(FromItem data, Integer level) throws JSQLParserException { - final List<FromItem> fromItems = new LinkedList<>(); - if (data instanceof net.sf.jsqlparser.schema.Table table) { - fromItems.add(data); - log.trace("from-item {} is of type table: level ~> {}", table.getName(), level); - return fromItems; - } - if (data instanceof SubJoin subJoin) { - log.trace("from-item is of type sub-join: level ~> {}", level); - for (Join join : subJoin.getJoinList()) { - final List<FromItem> tmp = fromItemToFromItems(join.getRightItem(), level + 1); - if (tmp == null) { - log.error("Failed to find right sub-join table: {}", join.getRightItem()); - throw new JSQLParserException("Failed to find right sub-join table"); - } - fromItems.addAll(tmp); - } - final List<FromItem> tmp = fromItemToFromItems(subJoin.getLeft(), level + 1); - if (tmp == null) { - log.error("Failed to find left sub-join table: {}", subJoin.getLeft()); - throw new JSQLParserException("Failed to find left sub-join table"); - } - fromItems.addAll(tmp); - return fromItems; - } - log.warn("unknown from-item {}", data); - return null; - } - default QueryDto resultSetToQueryDto(@NotNull ResultSet data) throws SQLException, QueryNotFoundException { /* note that next() is called outside this mapping function */ return QueryDto.builder() @@ -520,98 +354,6 @@ public interface DataMapper { return table; } - default Object dataColumnToObject(Object data, ColumnDto column) { - if (data == null) { - return null; - } - /* boolean encoding fix */ - if (column.getColumnType().equals(ColumnTypeDto.TINYINT) && column.getSize() == 1) { - column.setColumnType(ColumnTypeDto.BOOL); - } - switch (column.getColumnType()) { - case DATE -> { - final DateTimeFormatter formatter = new DateTimeFormatterBuilder() - .parseCaseInsensitive() /* case insensitive to parse JAN and FEB */ - .appendPattern("yyyy-MM-dd") - .toFormatter(Locale.ENGLISH); - final LocalDate date = LocalDate.parse(String.valueOf(data), formatter); - return date.atStartOfDay(ZoneId.of("UTC")) - .toInstant(); - } - case TIMESTAMP, DATETIME -> { - return Timestamp.valueOf(data.toString()) - .toInstant(); - } - case BINARY, VARBINARY, BIT -> { - return Long.parseLong(String.valueOf(data), 2); - } - case TEXT, CHAR, VARCHAR, TINYTEXT, MEDIUMTEXT, LONGTEXT, ENUM, SET -> { - return String.valueOf(data); - } - case BIGINT, SERIAL -> { - return new BigInteger(String.valueOf(data)); - } - case INT, SMALLINT, MEDIUMINT, TINYINT -> { - return Integer.parseInt(String.valueOf(data)); - } - case DECIMAL, FLOAT, DOUBLE -> { - return Double.valueOf(String.valueOf(data)); - } - case BOOL -> { - return Boolean.valueOf(String.valueOf(data)); - } - case TIME -> { - return String.valueOf(data); - } - case YEAR -> { - final String date = String.valueOf(data); - return Short.valueOf(date.substring(0, date.indexOf('-'))); - } - } - log.warn("column type {} is not known", column.getColumnType()); - throw new IllegalArgumentException("Column type not known"); - } - - default QueryResultDto resultListToQueryResultDto(List<ColumnDto> columns, ResultSet result) throws SQLException { - log.trace("mapping result list to query result, columns.size={}", columns.size()); - final List<Map<String, Object>> resultList = new LinkedList<>(); - while (result.next()) { - /* map the result set to the columns through the stored metadata in the metadata database */ - int[] idx = new int[]{1}; - final Map<String, Object> map = new HashMap<>(); - for (final ColumnDto column : columns) { - final String columnOrAlias; - if (column.getAlias() != null) { - log.debug("column {} has alias {}", column.getInternalName(), column.getAlias()); - columnOrAlias = column.getAlias(); - } else { - columnOrAlias = column.getInternalName(); - } - if (List.of(ColumnTypeDto.BLOB, ColumnTypeDto.TINYBLOB, ColumnTypeDto.MEDIUMBLOB, ColumnTypeDto.LONGBLOB).contains(column.getColumnType())) { - log.trace("column {} is of type {}", columnOrAlias, column.getColumnType().getType().toLowerCase()); - final Blob blob = result.getBlob(idx[0]++); - final String value = blob == null ? null : Hex.encodeHexString(blob.getBytes(1, (int) blob.length())).toUpperCase(); - map.put(columnOrAlias, value); - continue; - } - final Object object = dataColumnToObject(result.getObject(idx[0]++), column); - map.put(columnOrAlias, object); - } - 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.trace("created ordered header list: {}", headers); - return QueryResultDto.builder() - .result(resultList) - .headers(headers) - .build(); - } - default void prepareStatementWithColumnTypeObject(PreparedStatement ps, ColumnTypeDto columnType, int idx, Object value) throws SQLException { switch (columnType) { case BLOB, TINYBLOB, MEDIUMBLOB, LONGBLOB: 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 62a5fada07..99480719fa 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 @@ -221,12 +221,6 @@ public interface MariaDbMapper { return statement; } - default String tableCreateDtoToCreateSequenceRawQuery(at.tuwien.api.database.table.internal.TableCreateDto data) { - final String statement = "CREATE SEQUENCE IF NOT EXISTS `" + tableCreateDtoToSequenceName(data) + "` NOCACHE"; - log.trace("mapped create sequence statement: {}", statement); - return statement; - } - default String filterToGetQueriesRawQuery(Boolean filterPersisted) { final StringBuilder statement = new StringBuilder("SELECT `id`, `created`, `created_by`, `query`, `query_hash`, `result_hash`, `result_number`, `is_persisted`, `executed` FROM `qs_queries`"); if (filterPersisted != null) { @@ -427,6 +421,12 @@ public interface MariaDbMapper { return statement.toString(); } + default String selectExistsTableOrViewRawQuery() { + final String statement = "SELECT IF((SELECT 1 FROM information_schema.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?), 1, 0) AS `exists`"; + log.trace("mapped select exists table or view statement: {}", statement); + return statement; + } + default Long resultSetToNumber(ResultSet data) throws QueryMalformedException, SQLException { if (!data.next()) { throw new QueryMalformedException("Failed to map number"); @@ -434,38 +434,23 @@ public interface MariaDbMapper { return data.getLong(1); } - /** - * Selects the dataset page from a table/view. - * - * @param databaseName The database internal name. - * @param tableOrView The table/view internal name. - * @param columns The columns that should be contained in the result set. - * @param timestamp The moment in time the data should be returned in UTC timezone. - * @return The raw SQL query. - */ - default String selectDatasetRawQuery(String databaseName, String tableOrView, List<ColumnDto> columns, - Instant timestamp, Long size, Long page) { - final StringBuilder statement = new StringBuilder("SELECT ") - .append(String.join(",", columns.stream().map(c -> "`" + c.getInternalName() + "`").toList())); - statement.append(" FROM `") - .append(databaseName) - .append("`.`") - .append(tableOrView) - .append("`"); - if (timestamp != null) { - statement.append(" FOR SYSTEM_TIME AS OF TIMESTAMP '") - .append(mariaDbFormatter.format(timestamp)) - .append("'"); + default Boolean resultSetToBoolean(ResultSet data) throws QueryMalformedException, SQLException { + if (!data.next()) { + throw new QueryMalformedException("Failed to map boolean"); } - log.trace("pagination size/limit of {}", size); - statement.append(" LIMIT ") - .append(size); - log.trace("pagination page/offset of {}", page); - statement.append(" OFFSET ") - .append(page * size) - .append(";"); - log.trace("mapped select data query: {}", statement); - return statement.toString(); + return data.getBoolean(1); + } + + default List<Map<String, Object>> resultSetToList(ResultSet data, List<String> columns) throws SQLException { + final List<Map<String, Object>> list = new LinkedList<>(); + while (data.next()) { + final Map<String, Object> tuple = new HashMap<>(); + for (String column : columns) { + tuple.put(column, data.getString(column)); + } + list.add(tuple); + } + return list; } /** @@ -492,53 +477,6 @@ public interface MariaDbMapper { return "DROP TABLE `" + tableName + "`;"; } - default String tableOrViewToRawExportQuery(String databaseName, String tableOrView, List<String> columns, - Instant timestamp) { - final StringBuilder statement = new StringBuilder("(SELECT "); - int[] jdx = new int[]{0}; - columns.forEach(column -> { - statement.append(jdx[0] != 0 ? "," : "") - .append("`") - .append(column) - .append("`"); - jdx[0]++; - }); - statement.append(" FROM `") - .append(databaseName) - .append("`.`") - .append(tableOrView) - .append("`"); - if (timestamp != null) { - log.trace("export has timestamp present"); - statement.append(" FOR SYSTEM_TIME AS OF TIMESTAMP'") - .append(mariaDbFormatter.format(timestamp)) - .append("'"); - } - statement.append(") as tbl_alias"); - log.debug("mapped table/view export query: {}", statement); - return statement.toString(); - } - - default String subsetToRawTemporaryViewQuery(String viewName, String query) { - final StringBuilder statement = new StringBuilder("CREATE VIEW `") - .append(viewName) - .append("` AS (") - .append(query) - .append(");"); - log.debug("mapped temporary view query: {}", statement); - return statement.toString(); - } - - default String subsetToRawExportQuery(String viewName, Instant timestamp) { - final StringBuilder statement = new StringBuilder("(SELECT * FROM `") - .append(viewName) - .append("` FOR SYSTEM_TIME AS OF TIMESTAMP'") - .append(mariaDbFormatter.format(timestamp)) - .append("') as tbl"); - log.debug("mapped export query: {}", statement); - return statement.toString(); - } - default String temporaryTableToRawMergeQuery(String tmp, String table, List<String> columns) { final StringBuilder statement = new StringBuilder("INSERT INTO `") .append(table) 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 a585f2e98a..fb59360e97 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 @@ -9,6 +9,7 @@ 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; @@ -25,6 +26,10 @@ public interface MetadataMapper { org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MetadataMapper.class); + default String queryDtoToViewName(QueryDto subset) { + return subset.getQueryHash(); + } + PrivilegedContainerDto containerDtoToPrivilegedContainerDto(ContainerDto data); DatabaseDto privilegedDatabaseDtoToDatabaseDto(PrivilegedDatabaseDto data); 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 938da8820c..4a3455fbc4 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,13 +1,9 @@ package at.tuwien.service; -import at.tuwien.ExportResourceDto; -import at.tuwien.api.SortTypeDto; import at.tuwien.api.container.internal.PrivilegedContainerDto; import at.tuwien.api.database.internal.PrivilegedDatabaseDto; import at.tuwien.api.database.query.QueryDto; -import at.tuwien.api.database.query.QueryResultDto; import at.tuwien.exception.*; -import jakarta.validation.constraints.NotNull; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; @@ -30,49 +26,34 @@ public interface SubsetService { QueryStoreCreateException; /** - * Creates a subset from the given statement at given time in the given database. + * Retrieve data from a subset in a database and optionally paginate with number of page and size of results. * - * @param database The database. - * @param statement The subset statement. - * @param timestamp The timestamp as of which the data is queried. If smaller than <now>, historic data is queried. - * @param userId The user id of the creating user. - * @param page The page number. Optional but requires size to be set too. - * @param size The page size. Optional but requires page to be set too. - * @param sortDirection The sort direction. - * @param sortColumn The column that is sorted. - * @return The query result. - * @throws QueryStoreInsertException The query store refused to insert the query. - * @throws SQLException The connection to the database could not be established. - * @throws QueryNotFoundException The query was not found for re-execution. - * @throws TableMalformedException The table is malformed. - * @throws UserNotFoundException The user was not found. - * @throws NotAllowedException The operation is not allowed. - * @throws RemoteUnavailableException The privileged 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. + * @param database The database. + * @param subset The subset. + * @param page The page number. + * @param size Te result size. + * @return The data. + * @throws ViewMalformedException The view is malformed. + * @throws SQLException The connection to the database could not be established. + * @throws QueryMalformedException The mapped query produced a database error. + * @throws TableNotFoundException The database table is malformed. */ - QueryResultDto execute(PrivilegedDatabaseDto database, String statement, Instant timestamp, UUID userId, Long page, - Long size, SortTypeDto sortDirection, String sortColumn) - throws QueryStoreInsertException, SQLException, QueryNotFoundException, TableMalformedException, - UserNotFoundException, NotAllowedException, RemoteUnavailableException, DatabaseNotFoundException, - MetadataServiceException; + Dataset<Row> getData(PrivilegedDatabaseDto database, QueryDto subset, Long page, Long size) + throws ViewMalformedException, SQLException, QueryMalformedException, TableNotFoundException; /** - * Re-executes the query of a given subset in the given database. + * Creates a subset from the given statement at given time in the given database. * - * @param database The database. - * @param query The subset. - * @param page The page number. Optional but requires size to be set too. - * @param size The page size. Optional but requires page to be set too. - * @param sortDirection The sort direction. - * @param sortColumn The column that is sorted. - * @return The query result. - * @throws TableMalformedException The table is malformed. - * @throws SQLException The connection to the database could not be established. + * @param database The database. + * @param statement The subset statement. + * @param timestamp The timestamp as of which the data is queried. If smaller than <now>, historic data is queried. + * @param userId The user id of the creating user. + * @return The query id. + * @throws QueryStoreInsertException The query store refused to insert the query. + * @throws SQLException The connection to the database could not be established. */ - QueryResultDto reExecute(PrivilegedDatabaseDto database, QueryDto query, Long page, Long size, - SortTypeDto sortDirection, String sortColumn) throws TableMalformedException, - SQLException; + Long create(PrivilegedDatabaseDto database, String statement, Instant timestamp, UUID userId) + throws QueryStoreInsertException, SQLException; /** * Counts the subset row count of a query of a given subset in the given database. @@ -101,23 +82,6 @@ public interface SubsetService { List<QueryDto> findAll(PrivilegedDatabaseDto database, Boolean filterPersisted) throws SQLException, QueryNotFoundException, RemoteUnavailableException, DatabaseNotFoundException, MetadataServiceException; - /** - * Exports a subset by re-executing the query in a given database with given timestamp to a given s3key. - * - * @param database The database. - * @param query The query. - * @param timestamp The timestamp. - * @return The exported subset. - * @throws SQLException The connection to the database could not be established. - * @throws QueryMalformedException The mapped export query produced a database error. - * @throws StorageNotFoundException The exported subset was not found from the key provided by the sidecar in the Storage Service. - * @throws StorageUnavailableException The communication to the Storage Service failed. - * @throws RemoteUnavailableException The privileged database information could not be found in the Metadata Service. - */ - ExportResourceDto export(PrivilegedDatabaseDto database, QueryDto query, Instant timestamp) throws SQLException, - QueryMalformedException, StorageNotFoundException, StorageUnavailableException, RemoteUnavailableException, - ViewNotFoundException, MalformedException; - /** * Executes a subset query without saving it. * @@ -181,7 +145,4 @@ public interface SubsetService { * @throws QueryStoreGCException The query store failed to delete stale queries. */ void deleteStaleQueries(PrivilegedDatabaseDto database) throws SQLException, QueryStoreGCException; - - Dataset<Row> getData(@NotNull PrivilegedDatabaseDto database, String viewName, Instant timestamp) throws ViewNotFoundException, - QueryMalformedException; } 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 c70d1b703f..dc0b2041f0 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,14 +1,12 @@ package at.tuwien.service; -import at.tuwien.ExportResourceDto; +import at.tuwien.api.SortTypeDto; import at.tuwien.api.database.internal.PrivilegedDatabaseDto; import at.tuwien.api.database.query.ImportDto; -import at.tuwien.api.database.query.QueryResultDto; 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 jakarta.validation.constraints.NotNull; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; @@ -78,20 +76,6 @@ public interface TableService { */ void delete(PrivilegedTableDto table) throws SQLException, QueryMalformedException; - /** - * Obtains data from a table with given table object at timestamp, loaded as page number and length size. - * - * @param table The table object. - * @param timestamp The timestamp. - * @param page The page number. - * @param size The page size/length. - * @return The data. - * @throws SQLException Failed to parse SQL query, contains invalid syntax. - * @throws TableMalformedException The table schema is malformed, likely due to a bug in the application. - */ - QueryResultDto getPaginatedData(PrivilegedTableDto table, Instant timestamp, Long page, Long size) - throws SQLException, TableMalformedException; - /** * Obtains the table history for a given table object. * @@ -168,28 +152,7 @@ public interface TableService { void updateTuple(PrivilegedTableDto table, TupleUpdateDto data) throws SQLException, QueryMalformedException, TableMalformedException; - /** - * Exports a table at given system-versioning time. - * - * @param table The table. - * @param timestamp The system-versioning time. - * @return The exported resource. - * @throws TableNotFoundException The table was not found in the data database. - * @throws QueryMalformedException The export query is malformed, likely due to a bug in the application. - * @throws StorageUnavailableException Failed to establish a connection with the Storage Service. - */ - ExportResourceDto exportDataset(PrivilegedTableDto table, Instant timestamp) throws TableNotFoundException, - QueryMalformedException, StorageUnavailableException, MalformedException; - - /** - * Get data from a given table at timestamp. - * - * @param table The table. - * @param timestamp The timestamp. - * @return The data. - * @throws TableNotFoundException The table was not found in the data database. - * @throws QueryMalformedException The export query is malformed, likely due to a bug in the application. - */ - Dataset<Row> getData(@NotNull PrivilegedTableDto table, Instant timestamp) throws TableNotFoundException, - QueryMalformedException; + Dataset<Row> getData(PrivilegedDatabaseDto 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 26c04e401b..ec7a723261 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,16 +1,14 @@ package at.tuwien.service; -import at.tuwien.ExportResourceDto; 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.QueryResultDto; -import at.tuwien.api.database.table.internal.PrivilegedTableDto; -import at.tuwien.exception.*; -import jakarta.validation.constraints.NotNull; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; +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; @@ -18,6 +16,9 @@ import java.util.List; public interface ViewService { + Boolean existsByName(PrivilegedDatabaseDto database, String name) throws SQLException, + QueryMalformedException; + /** * Gets the metadata schema for a given database. * @@ -30,6 +31,16 @@ public interface ViewService { 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. * @@ -41,28 +52,15 @@ public interface ViewService { ViewDto create(PrivilegedDatabaseDto database, ViewCreateDto data) throws SQLException, ViewMalformedException; - /** - * Get data from the given view at specific timestamp, paginated by page and size. - * - * @param view The view. - * @param timestamp The timestamp. - * @param page The page number. - * @param size The page size. - * @return The data, if successful. - * @throws SQLException The connection to the data database was unsuccessful. - * @throws ViewMalformedException The query is malformed and was rejected by the data database. - */ - QueryResultDto data(PrivilegedViewDto view, Instant timestamp, Long page, Long size) throws SQLException, - ViewMalformedException; - /** * Deletes a view. * - * @param view The view. + * @param database The database. + * @param viewName The view name. * @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(PrivilegedViewDto view) throws SQLException, ViewMalformedException; + void delete(PrivilegedDatabaseDto database, String viewName) throws SQLException, ViewMalformedException; /** * Counts tuples in a view at system-versioned timestamp. @@ -74,27 +72,4 @@ public interface ViewService { * @throws QueryMalformedException The query is malformed and was rejected by the data database. */ Long count(PrivilegedViewDto view, Instant timestamp) throws SQLException, QueryMalformedException; - - /** - * Exports view data into a dataset. - * - * @param view The view. - * @return The dataset. - * @throws QueryMalformedException The query is malformed and was rejected by the data database. - * @throws StorageUnavailableException Failed to establish a connection with the Storage Service. - * @throws ViewNotFoundException The view with given name was not found. - */ - ExportResourceDto exportDataset(PrivilegedViewDto view) throws QueryMalformedException, - StorageUnavailableException, ViewNotFoundException, MalformedException; - - /** - * Get data from a given view. - * - * @param view The view. - * @return The data. - * @throws ViewNotFoundException The view with given name was not found. - * @throws QueryMalformedException The query is malformed and was rejected by the data database. - */ - Dataset<Row> getData(@NotNull PrivilegedViewDto view) throws ViewNotFoundException, - 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 7ed5150095..1493f579fd 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 @@ -43,22 +43,22 @@ public class AccessServiceMariaDbImpl extends HibernateConnector implements Acce long start = System.currentTimeMillis(); connection.prepareStatement(mariaDbMapper.databaseCreateUserQuery(user.getUsername(), user.getPassword())) .execute(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); /* grant access */ final String grants = access != AccessTypeDto.READ ? grantDefaultWrite : grantDefaultRead; start = System.currentTimeMillis(); connection.prepareStatement(mariaDbMapper.databaseGrantPrivilegesQuery(user.getUsername(), grants)) .execute(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); /* grant query store */ start = System.currentTimeMillis(); connection.prepareStatement(mariaDbMapper.databaseGrantProcedureQuery(user.getUsername(), "store_query")) .execute(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); /* apply access rights */ start = System.currentTimeMillis(); connection.prepareStatement(mariaDbMapper.databaseFlushPrivilegesQuery()); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); connection.commit(); } catch (SQLException e) { connection.rollback(); @@ -82,7 +82,7 @@ public class AccessServiceMariaDbImpl extends HibernateConnector implements Acce final long start = System.currentTimeMillis(); connection.prepareStatement(mariaDbMapper.databaseGrantPrivilegesQuery(user.getUsername(), grants)) .execute(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); /* apply access rights */ connection.prepareStatement(mariaDbMapper.databaseFlushPrivilegesQuery()); connection.commit(); @@ -106,12 +106,12 @@ public class AccessServiceMariaDbImpl extends HibernateConnector implements Acce long start = System.currentTimeMillis(); connection.prepareStatement(mariaDbMapper.databaseRevokePrivilegesQuery(user.getUsername())) .execute(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); /* apply access rights */ start = System.currentTimeMillis(); connection.prepareStatement(mariaDbMapper.databaseFlushPrivilegesQuery()) .execute(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); connection.commit(); } catch (SQLException e) { connection.rollback(); 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 b2a9b0c840..db446de281 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 @@ -40,7 +40,7 @@ public class DatabaseServiceMariaDbImpl extends HibernateConnector implements Da final long start = System.currentTimeMillis(); connection.prepareStatement(mariaDbMapper.databaseCreateDatabaseQuery(data.getInternalName())) .execute(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); connection.commit(); } catch (SQLException e) { connection.rollback(); @@ -76,7 +76,7 @@ public class DatabaseServiceMariaDbImpl extends HibernateConnector implements Da final long start = System.currentTimeMillis(); connection.prepareStatement(mariaDbMapper.databaseSetPasswordQuery(data.getUsername(), data.getPassword())) .execute(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); connection.commit(); } catch (SQLException e) { connection.rollback(); 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 index e2d085ae98..b52d1c792e 100644 --- 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 @@ -2,26 +2,10 @@ package at.tuwien.service.impl; import at.tuwien.api.container.internal.PrivilegedContainerDto; import at.tuwien.api.database.internal.PrivilegedDatabaseDto; -import at.tuwien.api.database.table.internal.PrivilegedTableDto; -import at.tuwien.config.S3Config; -import at.tuwien.exception.QueryMalformedException; -import at.tuwien.exception.TableNotFoundException; import com.mchange.v2.c3p0.ComboPooledDataSource; -import jakarta.validation.constraints.NotNull; import lombok.extern.log4j.Log4j2; -import org.apache.spark.SparkConf; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.apache.spark.sql.SparkSession; -import org.apache.spark.sql.catalyst.ExtendedAnalysisException; -import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Service; -import java.time.Instant; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; - @Log4j2 @Service public abstract class HibernateConnector { @@ -45,23 +29,6 @@ public abstract class HibernateConnector { return getPrivilegedDataSource(database.getContainer(), database.getInternalName()); } - public Map<String, String> getPrivilegedDatabaseOptions(PrivilegedTableDto table) { - return new HashMap<>() {{ - put("url", url(table.getDatabase().getContainer(), table.getDatabase().getInternalName())); - put("dbtable", table.getInternalName()); - put("user", table.getDatabase().getContainer().getUsername()); - put("password", table.getDatabase().getContainer().getPassword()); - }}; - } - - public String getHibernateUrl(PrivilegedContainerDto container, String databaseName) { - final StringBuilder sb = new StringBuilder(url(container, databaseName)) - .append("?currentSchema=") - .append(databaseName); - log.trace("mapped container to hibernate url: {}", sb.toString()); - return sb.toString(); - } - public String getSparkUrl(PrivilegedContainerDto container, String databaseName) { final StringBuilder sb = new StringBuilder(url(container, databaseName)) .append("?sessionVariables=sql_mode='ANSI_QUOTES'"); 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 797de65674..aff8513787 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 @@ -52,7 +52,7 @@ public class QueueServiceRabbitMqImpl extends HibernateConnector implements Queu } final long start = System.currentTimeMillis(); preparedStatement.executeUpdate(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); log.trace("successfully inserted tuple"); amqpDataAccessCounter.increment(); } finally { 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 index 1aba3c6b99..370d86e288 100644 --- 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 @@ -55,7 +55,7 @@ public class SchemaServiceMariaDbImpl extends HibernateConnector implements Sche statement1.setString(2, tableName); log.trace("1={}, 2={}", database.getInternalName(), tableName); TableDto table = dataMapper.schemaResultSetToTable(metadataMapper.privilegedDatabaseDtoToDatabaseDto(database), statement1.executeQuery()); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); /* obtain columns metadata */ start = System.currentTimeMillis(); final PreparedStatement statement2 = connection.prepareStatement(mariaDbMapper.databaseTableColumnsSelectRawQuery()); @@ -63,7 +63,7 @@ public class SchemaServiceMariaDbImpl extends HibernateConnector implements Sche statement2.setString(2, tableName); log.trace("1={}, 2={}", database.getInternalName(), tableName); final ResultSet resultSet2 = statement2.executeQuery(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); while (resultSet2.next()) { table = dataMapper.resultSetToTable(resultSet2, table, queryConfig); } @@ -74,7 +74,7 @@ public class SchemaServiceMariaDbImpl extends HibernateConnector implements Sche statement3.setString(2, tableName); log.trace("1={}, 2={}", database.getInternalName(), tableName); final ResultSet resultSet3 = statement3.executeQuery(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); while (resultSet3.next()) { final String clause = resultSet3.getString(1); table.getConstraints() @@ -89,7 +89,7 @@ public class SchemaServiceMariaDbImpl extends HibernateConnector implements Sche statement4.setString(2, tableName); log.trace("1={}, 2={}", database.getInternalName(), tableName); final ResultSet resultSet4 = statement4.executeQuery(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); while (resultSet4.next()) { table = dataMapper.resultSetToConstraint(resultSet4, table); for (UniqueDto uk : table.getConstraints().getUniques()) { @@ -136,7 +136,7 @@ public class SchemaServiceMariaDbImpl extends HibernateConnector implements Sche statement1.setString(2, viewName); log.trace("1={}, 2={}", database.getInternalName(), viewName); final ResultSet resultSet1 = statement1.executeQuery(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); if (!resultSet1.next()) { throw new ViewNotFoundException("Failed to find view in the information schema"); } @@ -152,7 +152,7 @@ public class SchemaServiceMariaDbImpl extends HibernateConnector implements Sche statement2.setString(2, viewName); log.trace("1={}, 2={}", database.getInternalName(), viewName); final ResultSet resultSet2 = statement2.executeQuery(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); TableDto tmp = TableDto.builder() .columns(new LinkedList<>()) .build(); 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 8c5cc6d601..1e1e78603b 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,13 +1,8 @@ package at.tuwien.service.impl; -import at.tuwien.ExportResourceDto; -import at.tuwien.api.SortTypeDto; import at.tuwien.api.container.internal.PrivilegedContainerDto; -import at.tuwien.api.database.ViewColumnDto; import at.tuwien.api.database.internal.PrivilegedDatabaseDto; import at.tuwien.api.database.query.QueryDto; -import at.tuwien.api.database.query.QueryResultDto; -import at.tuwien.api.database.table.columns.ColumnDto; import at.tuwien.api.identifier.IdentifierDto; import at.tuwien.api.identifier.IdentifierTypeDto; import at.tuwien.exception.*; @@ -15,56 +10,42 @@ import at.tuwien.gateway.MetadataServiceGateway; import at.tuwien.mapper.DataMapper; import at.tuwien.mapper.MariaDbMapper; import at.tuwien.mapper.MetadataMapper; -import at.tuwien.service.SchemaService; -import at.tuwien.service.StorageService; import at.tuwien.service.SubsetService; +import at.tuwien.service.TableService; +import at.tuwien.service.ViewService; import com.mchange.v2.c3p0.ComboPooledDataSource; -import io.micrometer.core.instrument.Counter; -import jakarta.validation.constraints.NotNull; import lombok.extern.log4j.Log4j2; -import net.sf.jsqlparser.JSQLParserException; -import org.apache.commons.lang3.RandomUtils; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; -import org.apache.spark.sql.SparkSession; -import org.apache.spark.sql.catalyst.ExtendedAnalysisException; -import org.sparkproject.guava.hash.Hashing; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import java.nio.charset.Charset; import java.sql.*; import java.time.Instant; import java.util.LinkedList; import java.util.List; -import java.util.Properties; import java.util.UUID; @Log4j2 @Service public class SubsetServiceMariaDbImpl extends HibernateConnector implements SubsetService { - private final Counter httpDataAccessCounter; private final DataMapper dataMapper; - private final SparkSession sparkSession; + private final ViewService viewService; + private final TableService tableService; private final MariaDbMapper mariaDbMapper; - private final SchemaService schemaService; private final MetadataMapper metadataMapper; - private final StorageService storageService; private final MetadataServiceGateway metadataServiceGateway; @Autowired - public SubsetServiceMariaDbImpl(Counter httpDataAccessCounter, DataMapper dataMapper, SparkSession sparkSession, - MariaDbMapper mariaDbMapper, SchemaService schemaService, - MetadataMapper metadataMapper, StorageService storageService, + public SubsetServiceMariaDbImpl(DataMapper dataMapper, ViewService viewService, TableService tableService, + MariaDbMapper mariaDbMapper, MetadataMapper metadataMapper, MetadataServiceGateway metadataServiceGateway) { - this.httpDataAccessCounter = httpDataAccessCounter; this.dataMapper = dataMapper; - this.sparkSession = sparkSession; + this.viewService = viewService; + this.tableService = tableService; this.mariaDbMapper = mariaDbMapper; - this.schemaService = schemaService; this.metadataMapper = metadataMapper; - this.storageService = storageService; this.metadataServiceGateway = metadataServiceGateway; } @@ -78,23 +59,23 @@ public class SubsetServiceMariaDbImpl extends HibernateConnector implements Subs long start = System.currentTimeMillis(); connection.prepareStatement(mariaDbMapper.queryStoreCreateSequenceRawQuery()) .execute(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); start = System.currentTimeMillis(); connection.prepareStatement(mariaDbMapper.queryStoreCreateTableRawQuery()) .execute(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); start = System.currentTimeMillis(); connection.prepareStatement(mariaDbMapper.queryStoreCreateHashTableProcedureRawQuery()) .execute(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); start = System.currentTimeMillis(); connection.prepareStatement(mariaDbMapper.queryStoreCreateStoreQueryProcedureRawQuery()) .execute(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); start = System.currentTimeMillis(); connection.prepareStatement(mariaDbMapper.queryStoreCreateInternalStoreQueryProcedureRawQuery()) .execute(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); connection.commit(); } catch (SQLException e) { connection.rollback(); @@ -107,32 +88,21 @@ public class SubsetServiceMariaDbImpl extends HibernateConnector implements Subs } @Override - public QueryResultDto execute(PrivilegedDatabaseDto database, String statement, Instant timestamp, - UUID userId, Long page, Long size, SortTypeDto sortDirection, String sortColumn) - throws QueryStoreInsertException, SQLException, QueryNotFoundException, TableMalformedException, - UserNotFoundException, NotAllowedException, RemoteUnavailableException, DatabaseNotFoundException, - MetadataServiceException { - final Long queryId = storeQuery(database, statement, timestamp, userId); - final QueryDto query = findById(database, queryId); - httpDataAccessCounter.increment(); - return reExecute(database, query, page, size, sortDirection, sortColumn); + public Dataset<Row> getData(PrivilegedDatabaseDto database, QueryDto subset, Long page, Long size) + throws ViewMalformedException, SQLException, QueryMalformedException, TableNotFoundException { + if (!viewService.existsByName(database, metadataMapper.queryDtoToViewName(subset))) { + log.warn("Missing internal view {} for subset with id {}: create it from subset query", metadataMapper.queryDtoToViewName(subset), subset.getId()); + viewService.create(database, subset); + } else { + log.debug("internal view {} for subset with id {} exists", metadataMapper.queryDtoToViewName(subset), subset.getId()); + } + return tableService.getData(database, metadataMapper.queryDtoToViewName(subset), subset.getExecution(), page, size, null, null); } @Override - public QueryResultDto reExecute(PrivilegedDatabaseDto database, QueryDto query, Long page, Long size, - SortTypeDto sortDirection, String sortColumn) throws TableMalformedException, - SQLException { - final List<ColumnDto> columns; - try { - columns = dataMapper.parseColumns(database.getId(), database.getTables(), query.getQuery()); - } catch (JSQLParserException e) { - log.error("Failed to map/parse columns: {}", e.getMessage()); - throw new TableMalformedException("Failed to map/parse columns: " + e.getMessage(), e); - } - final String statement = mariaDbMapper.selectRawSelectQuery(query.getQuery(), query.getExecution(), page, size); - final QueryResultDto dto = executeNonPersistent(database, statement, columns); - dto.setId(query.getId()); - return dto; + public Long create(PrivilegedDatabaseDto database, String statement, Instant timestamp, UUID userId) + throws QueryStoreInsertException, SQLException { + return storeQuery(database, statement, timestamp, userId); } @Override @@ -155,7 +125,7 @@ public class SubsetServiceMariaDbImpl extends HibernateConnector implements Subs log.trace("filter persisted only {}", filterPersisted); } final ResultSet resultSet = statement.executeQuery(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); final List<QueryDto> queries = new LinkedList<>(); while (resultSet.next()) { final QueryDto query = dataMapper.resultSetToQueryDto(resultSet); @@ -175,70 +145,6 @@ public class SubsetServiceMariaDbImpl extends HibernateConnector implements Subs } } - @Override - public ExportResourceDto export(PrivilegedDatabaseDto database, QueryDto query, Instant timestamp) - throws SQLException, QueryMalformedException, StorageNotFoundException, StorageUnavailableException, - RemoteUnavailableException, ViewNotFoundException, MalformedException { - final String viewName = "ex_" + Hashing.sha512() - .hashString(new String(RandomUtils.nextBytes(256), Charset.defaultCharset()), Charset.defaultCharset()) - .toString() - .substring(0, 60); - final ExportResourceDto export; - final ComboPooledDataSource dataSource = getPrivilegedDataSource(database); - final Connection connection = dataSource.getConnection(); - try { - /* export to data database sidecar */ - long start = System.currentTimeMillis(); - connection.prepareStatement(mariaDbMapper.subsetToRawTemporaryViewQuery(viewName, query.getQuery())) - .executeUpdate(); - log.debug("executed create view statement in {} ms", System.currentTimeMillis() - start); - start = System.currentTimeMillis(); - final List<String> columns = schemaService.inspectView(database, viewName) - .getColumns() - .stream() - .map(ViewColumnDto::getInternalName) - .toList(); - log.debug("executed inspect view columns statement in {} ms", System.currentTimeMillis() - start); - start = System.currentTimeMillis(); - final Dataset<Row> dataset = getData(database, viewName, timestamp) - .selectExpr(columns.toArray(new String[0])); - export = storageService.transformDataset(dataset); - log.debug("executed extract statement in {} ms", System.currentTimeMillis() - start); - start = System.currentTimeMillis(); - connection.prepareStatement(mariaDbMapper.dropViewRawQuery(viewName)) - .executeUpdate(); - log.debug("executed drop view statement in {} ms", System.currentTimeMillis() - start); - connection.commit(); - } catch (SQLException e) { - connection.rollback(); - log.error("Failed to execute query: {}", e.getMessage()); - throw new QueryMalformedException("Failed to execute query: " + e.getMessage(), e); - } finally { - dataSource.close(); - } - httpDataAccessCounter.increment(); - return export; - } - - public QueryResultDto executeNonPersistent(PrivilegedDatabaseDto database, String statement, - List<ColumnDto> columns) throws SQLException, TableMalformedException { - final ComboPooledDataSource dataSource = getPrivilegedDataSource(database); - final Connection connection = dataSource.getConnection(); - try { - final long start = System.currentTimeMillis(); - final PreparedStatement preparedStatement = connection.prepareStatement(statement); - final ResultSet resultSet = preparedStatement.executeQuery(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); - httpDataAccessCounter.increment(); - return dataMapper.resultListToQueryResultDto(columns, resultSet); - } catch (SQLException e) { - log.error("Failed to execute and map time-versioned query: {}", e.getMessage()); - throw new TableMalformedException("Failed to execute and map time-versioned query: " + e.getMessage(), e); - } finally { - dataSource.close(); - } - } - @Override public Long executeCountNonPersistent(PrivilegedDatabaseDto database, String statement, Instant timestamp) throws SQLException, QueryMalformedException, TableMalformedException { @@ -248,8 +154,7 @@ public class SubsetServiceMariaDbImpl extends HibernateConnector implements Subs final long start = System.currentTimeMillis(); final ResultSet resultSet = connection.prepareStatement(mariaDbMapper.countRawSelectQuery(statement, timestamp)) .executeQuery(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); - httpDataAccessCounter.increment(); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); return mariaDbMapper.resultSetToNumber(resultSet); } catch (SQLException e) { log.error("Failed to map object: {}", e.getMessage()); @@ -261,7 +166,7 @@ public class SubsetServiceMariaDbImpl extends HibernateConnector implements Subs @Override public QueryDto findById(PrivilegedDatabaseDto database, Long queryId) throws QueryNotFoundException, SQLException, - RemoteUnavailableException, UserNotFoundException, DatabaseNotFoundException, MetadataServiceException { + RemoteUnavailableException, DatabaseNotFoundException, MetadataServiceException { final ComboPooledDataSource dataSource = getPrivilegedDataSource(database); final Connection connection = dataSource.getConnection(); try { @@ -269,7 +174,7 @@ public class SubsetServiceMariaDbImpl extends HibernateConnector implements Subs final PreparedStatement preparedStatement = connection.prepareStatement(mariaDbMapper.queryStoreFindQueryRawQuery()); preparedStatement.setLong(1, queryId); final ResultSet resultSet = preparedStatement.executeQuery(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); if (!resultSet.next()) { throw new QueryNotFoundException("Failed to find query"); } @@ -306,7 +211,7 @@ public class SubsetServiceMariaDbImpl extends HibernateConnector implements Subs callableStatement.setTimestamp(3, Timestamp.from(timestamp)); callableStatement.registerOutParameter(4, Types.BIGINT); callableStatement.executeUpdate(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); queryId = callableStatement.getLong(4); callableStatement.close(); log.info("Stored query with id {} in database with name {}", queryId, database.getInternalName()); @@ -333,7 +238,7 @@ public class SubsetServiceMariaDbImpl extends HibernateConnector implements Subs preparedStatement.setBoolean(1, persist); preparedStatement.setLong(2, queryId); preparedStatement.executeUpdate(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); } catch (SQLException e) { log.error("Failed to (un-)persist query: {}", e.getMessage()); throw new QueryStorePersistException("Failed to (un-)persist query", e); @@ -351,7 +256,7 @@ public class SubsetServiceMariaDbImpl extends HibernateConnector implements Subs final long start = System.currentTimeMillis(); connection.prepareStatement(mariaDbMapper.queryStoreDeleteStaleQueriesRawQuery()) .executeUpdate(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); } catch (SQLException e) { log.error("Failed to delete stale queries: {}", e.getMessage()); throw new QueryStoreGCException("Failed to delete stale queries: " + e.getMessage(), e); @@ -360,27 +265,4 @@ public class SubsetServiceMariaDbImpl extends HibernateConnector implements Subs } } - @Override - public Dataset<Row> getData(@NotNull PrivilegedDatabaseDto database, String viewName, Instant timestamp) - throws ViewNotFoundException, QueryMalformedException { - log.debug("get data from view: {}", viewName); - try { - final Properties properties = new Properties(); - properties.setProperty("user", database.getContainer().getUsername()); - properties.setProperty("password", database.getContainer().getPassword()); - return sparkSession.read() - .jdbc(getSparkUrl(database.getContainer(), database.getInternalName()), - mariaDbMapper.subsetToRawExportQuery(viewName, timestamp), properties); - } catch (Exception e) { - if (e instanceof ExtendedAnalysisException exception) { - if (exception.getSimpleMessage().contains("TABLE_OR_VIEW_NOT_FOUND")) { - log.error("Failed to find temporary view {}: {}", viewName, exception.getSimpleMessage()); - throw new ViewNotFoundException("Failed to find temporary view " + viewName + ": " + exception.getSimpleMessage()) /* remove throwable on purpose, clutters the output */; - } - } - log.error("Failed to find get data from view: {}", e.getMessage()); - throw new QueryMalformedException("Failed to find get data from view: " + e.getMessage(), e); - } - } - } 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 0eea223f7e..4e7aa7dcfa 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,9 +1,8 @@ package at.tuwien.service.impl; -import at.tuwien.ExportResourceDto; +import at.tuwien.api.SortTypeDto; import at.tuwien.api.database.internal.PrivilegedDatabaseDto; import at.tuwien.api.database.query.ImportDto; -import at.tuwien.api.database.query.QueryResultDto; import at.tuwien.api.database.table.*; import at.tuwien.api.database.table.columns.ColumnDto; import at.tuwien.api.database.table.columns.ColumnStatisticDto; @@ -18,8 +17,6 @@ import at.tuwien.service.StorageService; import at.tuwien.service.TableService; import at.tuwien.utils.MariaDbUtil; import com.mchange.v2.c3p0.ComboPooledDataSource; -import io.micrometer.core.instrument.Counter; -import jakarta.validation.constraints.NotNull; import lombok.extern.log4j.Log4j2; import org.apache.spark.sql.*; import org.apache.spark.sql.catalyst.ExtendedAnalysisException; @@ -37,7 +34,6 @@ import java.util.*; @Service public class TableServiceMariaDbImpl extends HibernateConnector implements TableService { - private final Counter httpDataAccessCounter; private final DataMapper dataMapper; private final SparkSession sparkSession; private final MariaDbMapper mariaDbMapper; @@ -45,10 +41,8 @@ public class TableServiceMariaDbImpl extends HibernateConnector implements Table private final StorageService storageService; @Autowired - public TableServiceMariaDbImpl(Counter httpDataAccessCounter, DataMapper dataMapper, SparkSession sparkSession, - MariaDbMapper mariaDbMapper, SchemaService schemaService, - StorageService storageService) { - this.httpDataAccessCounter = httpDataAccessCounter; + public TableServiceMariaDbImpl(DataMapper dataMapper, SparkSession sparkSession, MariaDbMapper mariaDbMapper, + SchemaService schemaService, StorageService storageService) { this.dataMapper = dataMapper; this.sparkSession = sparkSession; this.mariaDbMapper = mariaDbMapper; @@ -68,7 +62,7 @@ public class TableServiceMariaDbImpl extends HibernateConnector implements Table final PreparedStatement statement = connection.prepareStatement(mariaDbMapper.databaseTablesSelectRawQuery()); statement.setString(1, database.getInternalName()); final ResultSet resultSet1 = statement.executeQuery(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + 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))) { @@ -106,7 +100,7 @@ public class TableServiceMariaDbImpl extends HibernateConnector implements Table } else { final ResultSet resultSet = connection.prepareStatement(query) .executeQuery(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); statistic = dataMapper.resultSetToTableStatistic(resultSet); final TableDto tmpTable = schemaService.inspectTable(table.getDatabase(), table.getInternalName()); statistic.setAvgRowLength(tmpTable.getAvgRowLength()); @@ -147,7 +141,7 @@ public class TableServiceMariaDbImpl extends HibernateConnector implements Table final long start = System.currentTimeMillis(); connection.prepareStatement(mariaDbMapper.tableCreateDtoToCreateTableRawQuery(data)) .execute(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); connection.commit(); } catch (SQLException e) { connection.rollback(); @@ -176,7 +170,7 @@ public class TableServiceMariaDbImpl extends HibernateConnector implements Table final long start = System.currentTimeMillis(); connection.prepareStatement(mariaDbMapper.dropTableRawQuery(tableName)) .execute(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); connection.commit(); } catch (SQLException e) { connection.rollback(); @@ -188,37 +182,6 @@ public class TableServiceMariaDbImpl extends HibernateConnector implements Table log.info("Deleted table with name {}", tableName); } - @Override - public QueryResultDto getPaginatedData(PrivilegedTableDto table, Instant timestamp, Long page, Long size) throws SQLException, - TableMalformedException { - final ComboPooledDataSource dataSource = getPrivilegedDataSource(table.getDatabase()); - final Connection connection = dataSource.getConnection(); - final QueryResultDto queryResult; - try { - /* find table data */ - long start = System.currentTimeMillis(); - final ResultSet resultSet = connection.prepareStatement(mariaDbMapper.selectDatasetRawQuery( - table.getDatabase().getInternalName(), table.getInternalName(), table.getColumns(), - timestamp, size, page)) - .executeQuery(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); - start = System.currentTimeMillis(); - connection.commit(); - queryResult = dataMapper.resultListToQueryResultDto(table.getColumns(), resultSet); - log.debug("mapped result in {} ms", System.currentTimeMillis() - start); - httpDataAccessCounter.increment(); - } catch (SQLException e) { - connection.rollback(); - log.error("Failed to find data from table {}.{}: {}", table.getDatabase().getInternalName(), table.getInternalName(), e.getMessage()); - throw new TableMalformedException("Failed to find data from table " + table.getDatabase().getInternalName() + "." + table.getInternalName() + ": " + e.getMessage(), e); - } finally { - dataSource.close(); - } - log.info("Find data from table {}.{}", table.getDatabase().getInternalName(), table.getInternalName()); - queryResult.setId(table.getId()); - return queryResult; - } - @Override public List<TableHistoryDto> history(PrivilegedTableDto table, Long size) throws SQLException, TableNotFoundException { @@ -231,7 +194,7 @@ public class TableServiceMariaDbImpl extends HibernateConnector implements Table final ResultSet resultSet = connection.prepareStatement(mariaDbMapper.selectHistoryRawQuery( table.getDatabase().getInternalName(), table.getInternalName(), size)) .executeQuery(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); history = dataMapper.resultSetToTableHistory(resultSet); connection.commit(); } catch (SQLException e) { @@ -257,7 +220,7 @@ public class TableServiceMariaDbImpl extends HibernateConnector implements Table final ResultSet resultSet = connection.prepareStatement(mariaDbMapper.selectCountRawQuery( table.getDatabase().getInternalName(), table.getInternalName(), timestamp)) .executeQuery(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); queryResult = mariaDbMapper.resultSetToNumber(resultSet); connection.commit(); } catch (SQLException e) { @@ -338,7 +301,7 @@ public class TableServiceMariaDbImpl extends HibernateConnector implements Table } final long start = System.currentTimeMillis(); statement.executeUpdate(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); connection.commit(); } catch (SQLException e) { connection.rollback(); @@ -382,7 +345,7 @@ public class TableServiceMariaDbImpl extends HibernateConnector implements Table } final long start = System.currentTimeMillis(); statement.executeUpdate(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); connection.commit(); } catch (SQLException e) { connection.rollback(); @@ -418,7 +381,7 @@ public class TableServiceMariaDbImpl extends HibernateConnector implements Table } final long start = System.currentTimeMillis(); statement.executeUpdate(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); connection.commit(); } catch (SQLException e) { connection.rollback(); @@ -442,34 +405,24 @@ public class TableServiceMariaDbImpl extends HibernateConnector implements Table } @Override - public ExportResourceDto exportDataset(PrivilegedTableDto table, Instant timestamp) throws TableNotFoundException, - QueryMalformedException, StorageUnavailableException, MalformedException { - final Dataset<Row> dataset = getData(table, timestamp); - httpDataAccessCounter.increment(); - return storageService.transformDataset(dataset); - } - - @Override - public Dataset<Row> getData(@NotNull PrivilegedTableDto table, Instant timestamp) throws TableNotFoundException, - QueryMalformedException { + public Dataset<Row> getData(PrivilegedDatabaseDto database, String tableOrView, Instant timestamp, + Long page, Long size, SortTypeDto sortDirection, String sortColumn) + throws QueryMalformedException, TableNotFoundException { try { final Properties properties = new Properties(); - properties.setProperty("user", table.getDatabase().getContainer().getUsername()); - properties.setProperty("password", table.getDatabase().getContainer().getPassword()); + properties.setProperty("user", database.getContainer().getUsername()); + properties.setProperty("password", database.getContainer().getPassword()); return sparkSession.read() - .jdbc(getSparkUrl(table.getDatabase().getContainer(), table.getDatabase().getInternalName()), - mariaDbMapper.tableOrViewToRawExportQuery(table.getDatabase().getInternalName(), - table.getInternalName(), table.getColumns().stream() - .map(ColumnDto::getInternalName).toList(), timestamp), properties); + .jdbc(getSparkUrl(database.getContainer(), database.getInternalName()), tableOrView, properties); } catch (Exception e) { if (e instanceof ExtendedAnalysisException exception) { if (exception.getSimpleMessage().contains("TABLE_OR_VIEW_NOT_FOUND")) { - log.error("Failed to find table {}: {}", table.getInternalName(), exception.getSimpleMessage()); - throw new TableNotFoundException("Failed to find table " + table.getInternalName() + ": " + exception.getSimpleMessage()) /* remove throwable on purpose, clutters the output */; + log.error("Failed to find named reference: {}", exception.getSimpleMessage()); + throw new TableNotFoundException("Failed to find named reference: " + exception.getSimpleMessage()) /* remove throwable on purpose, clutters the output */; } } - log.error("Failed to export dataset: {}", e.getMessage()); - throw new QueryMalformedException("Failed to export dataset: " + e.getMessage(), e); + log.error("Failed to find get data from query statement: {}", e.getMessage()); + throw new QueryMalformedException("Failed to find get data from query statement: " + e.getMessage(), e); } } 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 7ca776ff8a..a4cb031066 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,30 +1,23 @@ package at.tuwien.service.impl; -import at.tuwien.ExportResourceDto; -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.database.query.QueryResultDto; -import at.tuwien.api.database.table.columns.ColumnDto; +import at.tuwien.api.database.query.QueryDto; import at.tuwien.config.QueryConfig; -import at.tuwien.exception.*; +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.StorageService; import at.tuwien.service.ViewService; import com.google.common.hash.Hashing; import com.mchange.v2.c3p0.ComboPooledDataSource; -import io.micrometer.core.instrument.Counter; -import jakarta.validation.constraints.NotNull; import lombok.extern.log4j.Log4j2; -import org.apache.spark.sql.Dataset; -import org.apache.spark.sql.Row; -import org.apache.spark.sql.SparkSession; -import org.apache.spark.sql.catalyst.ExtendedAnalysisException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -36,36 +29,51 @@ import java.sql.SQLException; import java.time.Instant; import java.util.LinkedList; import java.util.List; -import java.util.Properties; @Log4j2 @Service public class ViewServiceMariaDbImpl extends HibernateConnector implements ViewService { - private final Counter httpDataAccessCounter; private final DataMapper dataMapper; private final QueryConfig queryConfig; - private final SparkSession sparkSession; - private final MariaDbMapper mariaDbMapper; private final SchemaService schemaService; - private final StorageService storageService; + private final MariaDbMapper mariaDbMapper; private final MetadataMapper metadataMapper; @Autowired - public ViewServiceMariaDbImpl(Counter httpDataAccessCounter, DataMapper dataMapper, - QueryConfig queryConfig, SparkSession sparkSession, MariaDbMapper mariaDbMapper, - SchemaService schemaService, StorageService storageService, - MetadataMapper metadataMapper) { - this.httpDataAccessCounter = httpDataAccessCounter; + public ViewServiceMariaDbImpl(DataMapper dataMapper, QueryConfig queryConfig, SchemaService schemaService, + MariaDbMapper mariaDbMapper, MetadataMapper metadataMapper) { this.dataMapper = dataMapper; this.queryConfig = queryConfig; - this.sparkSession = sparkSession; - this.mariaDbMapper = mariaDbMapper; this.schemaService = schemaService; - this.storageService = storageService; + 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 { @@ -78,9 +86,13 @@ public class ViewServiceMariaDbImpl extends HibernateConnector implements ViewSe statement.setString(1, database.getInternalName()); final long start = System.currentTimeMillis(); final ResultSet resultSet1 = statement.executeQuery(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + 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; @@ -101,6 +113,17 @@ public class ViewServiceMariaDbImpl extends HibernateConnector implements ViewSe 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 { @@ -127,7 +150,7 @@ public class ViewServiceMariaDbImpl extends HibernateConnector implements ViewSe final long start = System.currentTimeMillis(); connection.prepareStatement(mariaDbMapper.viewCreateRawQuery(view.getInternalName(), data.getQuery())) .execute(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); /* select view columns */ final PreparedStatement statement2 = connection.prepareStatement(mariaDbMapper.databaseTableColumnsSelectRawQuery()); statement2.setString(1, database.getInternalName()); @@ -149,47 +172,15 @@ public class ViewServiceMariaDbImpl extends HibernateConnector implements ViewSe } @Override - public QueryResultDto data(PrivilegedViewDto view, Instant timestamp, Long page, Long size) throws SQLException, - ViewMalformedException { - final ComboPooledDataSource dataSource = getPrivilegedDataSource(view.getDatabase()); - final Connection connection = dataSource.getConnection(); - final QueryResultDto queryResult; - try { - /* find table data */ - final List<ColumnDto> mappedColumns = view.getColumns() - .stream() - .map(metadataMapper::viewColumnDtoToColumnDto) - .toList(); - final long start = System.currentTimeMillis(); - final ResultSet resultSet = connection.prepareStatement( - mariaDbMapper.selectDatasetRawQuery(view.getDatabase().getInternalName(), - view.getInternalName(), mappedColumns, timestamp, size, page)) - .executeQuery(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); - queryResult = dataMapper.resultListToQueryResultDto(mappedColumns, resultSet); - queryResult.setId(view.getId()); - connection.commit(); - httpDataAccessCounter.increment(); - } catch (SQLException e) { - log.error("Failed to map object: {}", e.getMessage()); - throw new ViewMalformedException("Failed to map object: " + e.getMessage(), e); - } finally { - dataSource.close(); - } - log.info("Find data from view {}.{}", view.getDatabase().getInternalName(), view.getInternalName()); - return queryResult; - } - - @Override - public void delete(PrivilegedViewDto view) throws SQLException, ViewMalformedException { - final ComboPooledDataSource dataSource = getPrivilegedDataSource(view.getDatabase()); + public void delete(PrivilegedDatabaseDto database, String viewName) throws SQLException, ViewMalformedException { + final ComboPooledDataSource dataSource = getPrivilegedDataSource(database); final Connection connection = dataSource.getConnection(); try { /* drop view if exists */ final long start = System.currentTimeMillis(); - connection.prepareStatement(mariaDbMapper.dropViewRawQuery(view.getInternalName())) + connection.prepareStatement(mariaDbMapper.dropViewRawQuery(viewName)) .execute(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); connection.commit(); } catch (SQLException e) { connection.rollback(); @@ -198,7 +189,7 @@ public class ViewServiceMariaDbImpl extends HibernateConnector implements ViewSe } finally { dataSource.close(); } - log.info("Deleted view {}.{}", view.getDatabase().getInternalName(), view.getInternalName()); + log.info("Deleted view {}.{}", database.getInternalName(), viewName); } @@ -214,7 +205,7 @@ public class ViewServiceMariaDbImpl extends HibernateConnector implements ViewSe final ResultSet resultSet = connection.prepareStatement(mariaDbMapper.selectCountRawQuery( view.getDatabase().getInternalName(), view.getInternalName(), timestamp)) .executeQuery(); - log.debug("executed statement in {} ms", System.currentTimeMillis() - start); + log.trace("executed statement in {} ms", System.currentTimeMillis() - start); queryResult = mariaDbMapper.resultSetToNumber(resultSet); connection.commit(); } catch (SQLException e) { @@ -228,36 +219,4 @@ public class ViewServiceMariaDbImpl extends HibernateConnector implements ViewSe return queryResult; } - @Override - public ExportResourceDto exportDataset(PrivilegedViewDto view) throws QueryMalformedException, - StorageUnavailableException, ViewNotFoundException, MalformedException { - final Dataset<Row> dataset = getData(view); - httpDataAccessCounter.increment(); - return storageService.transformDataset(dataset); - } - - @Override - public Dataset<Row> getData(@NotNull PrivilegedViewDto view) throws ViewNotFoundException, - QueryMalformedException { - try { - final Properties properties = new Properties(); - properties.setProperty("user", view.getDatabase().getContainer().getUsername()); - properties.setProperty("password", view.getDatabase().getContainer().getPassword()); - return sparkSession.read() - .jdbc(getSparkUrl(view.getDatabase().getContainer(), view.getDatabase().getInternalName()), - mariaDbMapper.tableOrViewToRawExportQuery(view.getDatabase().getInternalName(), - view.getInternalName(), view.getColumns().stream() - .map(ViewColumnDto::getInternalName).toList(), Instant.now()), properties); - } catch (Exception e) { - if (e instanceof ExtendedAnalysisException exception) { - if (exception.getSimpleMessage().contains("TABLE_OR_VIEW_NOT_FOUND")) { - log.error("Failed to find view {}: {}", view.getInternalName(), exception.getSimpleMessage()); - throw new ViewNotFoundException("Failed to find view " + view.getInternalName() + ": " + exception.getSimpleMessage()) /* remove throwable on purpose, clutters the output */; - } - } - log.error("Failed to export dataset: {}", e.getMessage()); - throw new QueryMalformedException("Failed to export dataset: " + e.getMessage(), e); - } - } - } diff --git a/dbrepo-metadata-service/api/pom.xml b/dbrepo-metadata-service/api/pom.xml index 9196e2d228..42dec67115 100644 --- a/dbrepo-metadata-service/api/pom.xml +++ b/dbrepo-metadata-service/api/pom.xml @@ -6,18 +6,18 @@ <parent> <groupId>at.tuwien</groupId> <artifactId>dbrepo-metadata-service</artifactId> - <version>1.5.2</version> + <version>1.5.3</version> </parent> <artifactId>dbrepo-metadata-service-api</artifactId> <name>dbrepo-metadata-service-api</name> - <version>1.5.2</version> + <version>1.5.3</version> <dependencies> <dependency> <groupId>at.tuwien</groupId> <artifactId>dbrepo-metadata-service-entities</artifactId> - <version>1.5.2</version> + <version>1.5.3</version> <scope>compile</scope> </dependency> </dependencies> diff --git a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/ViewCreateDto.java b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/ViewCreateDto.java index 583b5f0d81..4041292b51 100644 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/ViewCreateDto.java +++ b/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/ViewCreateDto.java @@ -19,7 +19,7 @@ import lombok.extern.jackson.Jacksonized; public class ViewCreateDto { @NotBlank - @Size(min = 1, max = 64) + @Size(min = 1, max = 63) @Schema(example = "Air Quality") private String name; 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 deleted file mode 100644 index 90f2d1a6ce..0000000000 --- a/dbrepo-metadata-service/api/src/main/java/at/tuwien/api/database/query/QueryResultDto.java +++ /dev/null @@ -1,29 +0,0 @@ -package at.tuwien.api.database.query; - -import lombok.*; - -import jakarta.validation.constraints.NotNull; -import lombok.extern.jackson.Jacksonized; - -import java.util.List; -import java.util.Map; - -@Getter -@Setter -@Builder -@NoArgsConstructor -@AllArgsConstructor -@Jacksonized -@ToString -public class QueryResultDto { - - @NotNull - private List<Map<String, Object>> result; - - @NotNull - private List<Map<String, Integer>> headers; - - @NotNull - private Long id; - -} diff --git a/dbrepo-metadata-service/entities/pom.xml b/dbrepo-metadata-service/entities/pom.xml index c308f0163b..f7f575f633 100644 --- a/dbrepo-metadata-service/entities/pom.xml +++ b/dbrepo-metadata-service/entities/pom.xml @@ -6,12 +6,12 @@ <parent> <groupId>at.tuwien</groupId> <artifactId>dbrepo-metadata-service</artifactId> - <version>1.5.2</version> + <version>1.5.3</version> </parent> <artifactId>dbrepo-metadata-service-entities</artifactId> <name>dbrepo-metadata-service-entity</name> - <version>1.5.2</version> + <version>1.5.3</version> <dependencies/> diff --git a/dbrepo-metadata-service/oai/pom.xml b/dbrepo-metadata-service/oai/pom.xml index fddf87ecb4..d08e19730e 100644 --- a/dbrepo-metadata-service/oai/pom.xml +++ b/dbrepo-metadata-service/oai/pom.xml @@ -6,12 +6,12 @@ <parent> <groupId>at.tuwien</groupId> <artifactId>dbrepo-metadata-service</artifactId> - <version>1.5.2</version> + <version>1.5.3</version> </parent> <artifactId>dbrepo-metadata-service-oai</artifactId> <name>dbrepo-metadata-service-oai</name> - <version>1.5.2</version> + <version>1.5.3</version> <dependencies/> diff --git a/dbrepo-metadata-service/pom.xml b/dbrepo-metadata-service/pom.xml index 56ab43349a..025be479bb 100644 --- a/dbrepo-metadata-service/pom.xml +++ b/dbrepo-metadata-service/pom.xml @@ -5,13 +5,13 @@ <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> - <version>3.1.12</version> + <version>3.3.5</version> </parent> <groupId>at.tuwien</groupId> <artifactId>dbrepo-metadata-service</artifactId> <name>dbrepo-metadata-service</name> - <version>1.5.2</version> + <version>1.5.3</version> <description>Service that manages the metadata</description> @@ -38,11 +38,11 @@ <properties> <java.version>17</java.version> - <spring-cloud.version>4.0.2</spring-cloud.version> + <spring-cloud.version>4.1.4</spring-cloud.version> <mapstruct.version>1.5.5.Final</mapstruct.version> <rabbitmq.version>5.20.0</rabbitmq.version> <jackson-datatype.version>2.15.0</jackson-datatype.version> - <commons-io.version>2.15.0</commons-io.version> + <commons-io.version>2.17.0</commons-io.version> <commons-validator.version>1.8.0</commons-validator.version> <guava.version>33.0.0-jre</guava.version> <jacoco.version>0.8.12</jacoco.version> @@ -55,6 +55,7 @@ <keycloak.version>21.0.2</keycloak.version> <springdoc-openapi.version>2.3.0</springdoc-openapi.version> <testcontainers.version>1.19.1</testcontainers.version> + <jackson.version>2.15.2</jackson.version> <keycloak-testcontainer.version>3.2.0</keycloak-testcontainer.version> <aws-s3.version>2.25.23</aws-s3.version> <jackson.version>2.15.2</jackson.version> diff --git a/dbrepo-metadata-service/report/pom.xml b/dbrepo-metadata-service/report/pom.xml index 95ea8b8b97..f49b7a292a 100644 --- a/dbrepo-metadata-service/report/pom.xml +++ b/dbrepo-metadata-service/report/pom.xml @@ -6,12 +6,12 @@ <parent> <artifactId>dbrepo-metadata-service</artifactId> <groupId>at.tuwien</groupId> - <version>1.5.2</version> + <version>1.5.3</version> </parent> <artifactId>dbrepo-metadata-service-report</artifactId> <name>dbrepo-metadata-service-report</name> - <version>1.5.2</version> + <version>1.5.3</version> <dependencies> <dependency> diff --git a/dbrepo-metadata-service/repositories/pom.xml b/dbrepo-metadata-service/repositories/pom.xml index 816f71a1cb..a6d7bcc281 100644 --- a/dbrepo-metadata-service/repositories/pom.xml +++ b/dbrepo-metadata-service/repositories/pom.xml @@ -6,12 +6,12 @@ <parent> <artifactId>dbrepo-metadata-service</artifactId> <groupId>at.tuwien</groupId> - <version>1.5.2</version> + <version>1.5.3</version> </parent> <artifactId>dbrepo-metadata-service-repositories</artifactId> <name>dbrepo-metadata-service-repositories</name> - <version>1.5.2</version> + <version>1.5.3</version> <dependencies> <dependency> diff --git a/dbrepo-metadata-service/rest-service/pom.xml b/dbrepo-metadata-service/rest-service/pom.xml index 613d4c4d4a..0be7176e7a 100644 --- a/dbrepo-metadata-service/rest-service/pom.xml +++ b/dbrepo-metadata-service/rest-service/pom.xml @@ -6,12 +6,12 @@ <parent> <artifactId>dbrepo-metadata-service</artifactId> <groupId>at.tuwien</groupId> - <version>1.5.2</version> + <version>1.5.3</version> </parent> <artifactId>dbrepo-metadata-service-rest-service</artifactId> <name>dbrepo-metadata-service-rest</name> - <version>1.5.2</version> + <version>1.5.3</version> <dependencies> <dependency> diff --git a/dbrepo-metadata-service/rest-service/src/main/resources/application.yml b/dbrepo-metadata-service/rest-service/src/main/resources/application.yml index 2d79e7cfae..290864eea9 100644 --- a/dbrepo-metadata-service/rest-service/src/main/resources/application.yml +++ b/dbrepo-metadata-service/rest-service/src/main/resources/application.yml @@ -48,21 +48,6 @@ logging: at.tuwien.: "${LOG_LEVEL:info}" org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver: debug dbrepo: - repository-name: "${REPOSITORY_NAME:Database Repository}" - base-url: "${BASE_URL:http://localhost}" - admin-email: "${ADMIN_EMAIL:noreply@example.com}" - deleted-record: "${DELETED_RECORD:persistent}" - granularity: "${GRANULARITY:YYYY-MM-DDThh:mm:ssZ}" - exchangeName: "${BROKER_EXCHANGE_NAME:dbrepo}" - queueName: "${BROKER_QUEUE_NAME:dbrepo}" - connectionTimeout: "${SPARQL_CONNECTION_TIMEOUT:10000}" - s3: - accessKeyId: "${S3_ACCESS_KEY_ID:seaweedfsadmin}" - secretAccessKey: "${S3_SECRET_ACCESS_KEY:seaweedfsadmin}" - bucket: "${S3_BUCKET:dbrepo}" - system: - username: "${SYSTEM_USERNAME:admin}" - password: "${SYSTEM_PASSWORD:admin}" endpoints: analyseService: "${ANALYSE_SERVICE_ENDPOINT:http://analyse-service:8080}" searchService: "${SEARCH_SERVICE_ENDPOINT:http://search-service:8080}" @@ -72,6 +57,13 @@ dbrepo: storageService: "${S3_ENDPOINT:http://storage-service:9000}" rorService: "${ROR_ENDPOINT:https://api.ror.org}" crossRefService: "${CROSSREF_ENDPOINT:http://data.crossref.org}" + s3: + accessKeyId: "${S3_ACCESS_KEY_ID:seaweedfsadmin}" + secretAccessKey: "${S3_SECRET_ACCESS_KEY:seaweedfsadmin}" + bucket: "${S3_BUCKET:dbrepo}" + system: + username: "${SYSTEM_USERNAME:admin}" + password: "${SYSTEM_PASSWORD:admin}" pid: base: "${BASE_URL:http://localhost}/pid/" jwt: @@ -82,3 +74,11 @@ dbrepo: client: "${AUTH_SERVICE_CLIENT:dbrepo-client}" clientSecret: "${AUTH_SERVICE_CLIENT_SECRET:MUwRc7yfXSJwX8AdRMWaQC3Nep1VjwgG}" website: "${BASE_URL:http://localhost}" + repository-name: "${REPOSITORY_NAME:Database Repository}" + base-url: "${BASE_URL:http://localhost}" + admin-email: "${ADMIN_EMAIL:noreply@example.com}" + deleted-record: "${DELETED_RECORD:persistent}" + granularity: "${GRANULARITY:YYYY-MM-DDThh:mm:ssZ}" + exchangeName: "${BROKER_EXCHANGE_NAME:dbrepo}" + queueName: "${BROKER_QUEUE_NAME:dbrepo}" + connectionTimeout: "${SPARQL_CONNECTION_TIMEOUT:10000}" 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 c849900985..108801e53f 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 @@ -217,7 +217,7 @@ public class MetadataMapperUnitTest extends AbstractUnitTest { final TableDto table0 = response.getTables().get(0); assertEquals(TABLE_1_ID, table0.getId()); assertEquals(TABLE_1_NAME, table0.getName()); - assertEquals(TABLE_1_INTERNALNAME, table0.getInternalName()); + assertEquals(TABLE_1_INTERNAL_NAME, table0.getInternalName()); assertEquals(TABLE_1_DESCRIPTION, table0.getDescription()); assertEquals(DATABASE_1_ID, table0.getTdbid()); assertEquals(USER_1_ID, table0.getCreatedBy()); diff --git a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/mvc/AuthenticationPrivilegedIntegrationMvcTest.java b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/mvc/AuthenticationPrivilegedIntegrationMvcTest.java index 6a01ae6fec..22e921f534 100644 --- a/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/mvc/AuthenticationPrivilegedIntegrationMvcTest.java +++ b/dbrepo-metadata-service/rest-service/src/test/java/at/tuwien/mvc/AuthenticationPrivilegedIntegrationMvcTest.java @@ -166,7 +166,7 @@ public class AuthenticationPrivilegedIntegrationMvcTest extends AbstractUnitTest .andExpect(header().string("X-Port", "" + CONTAINER_1_PORT)) .andExpect(header().string("X-Type", IMAGE_1_JDBC)) .andExpect(header().string("X-Database", DATABASE_1_INTERNALNAME)) - .andExpect(header().string("X-Table", TABLE_1_INTERNALNAME)) + .andExpect(header().string("X-Table", TABLE_1_INTERNAL_NAME)) .andExpect(header().string("Access-Control-Expose-Headers", "X-Username X-Password X-Host X-Port X-Type X-Database X-Table")) .andExpect(status().isOk()); } @@ -184,7 +184,7 @@ public class AuthenticationPrivilegedIntegrationMvcTest extends AbstractUnitTest .andExpect(header().string("X-Port", "" + CONTAINER_1_PORT)) .andExpect(header().string("X-Type", IMAGE_1_JDBC)) .andExpect(header().string("X-Database", DATABASE_1_INTERNALNAME)) - .andExpect(header().string("X-Table", TABLE_1_INTERNALNAME)) + .andExpect(header().string("X-Table", TABLE_1_INTERNAL_NAME)) .andExpect(header().string("Access-Control-Expose-Headers", "X-Username X-Password X-Host X-Port X-Type X-Database X-Table")) .andExpect(status().isOk()); } 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 551a6c350a..01fc883bc8 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 @@ -107,7 +107,7 @@ public class TableServiceUnitTest extends AbstractUnitTest { /* test */ assertThrows(DatabaseNotFoundException.class, () -> { - tableService.findByName(DATABASE_3_ID, TABLE_1_INTERNALNAME); + tableService.findByName(DATABASE_3_ID, TABLE_1_INTERNAL_NAME); }); } diff --git a/dbrepo-metadata-service/services/pom.xml b/dbrepo-metadata-service/services/pom.xml index d98e575eb9..de48899cbe 100644 --- a/dbrepo-metadata-service/services/pom.xml +++ b/dbrepo-metadata-service/services/pom.xml @@ -6,12 +6,12 @@ <parent> <artifactId>dbrepo-metadata-service</artifactId> <groupId>at.tuwien</groupId> - <version>1.5.2</version> + <version>1.5.3</version> </parent> <artifactId>dbrepo-metadata-service-services</artifactId> <name>dbrepo-metadata-service-services</name> - <version>1.5.2</version> + <version>1.5.3</version> <dependencies> <dependency> diff --git a/dbrepo-metadata-service/test/pom.xml b/dbrepo-metadata-service/test/pom.xml index dcccbef8f8..49cff9ddcb 100644 --- a/dbrepo-metadata-service/test/pom.xml +++ b/dbrepo-metadata-service/test/pom.xml @@ -6,12 +6,12 @@ <parent> <groupId>at.tuwien</groupId> <artifactId>dbrepo-metadata-service</artifactId> - <version>1.5.2</version> + <version>1.5.3</version> </parent> <artifactId>dbrepo-metadata-service-test</artifactId> <name>dbrepo-metadata-service-test</name> - <version>1.5.2</version> + <version>1.5.3</version> <dependencies> <dependency> 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 8e613cfa7c..b674e7252a 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 @@ -122,6 +122,7 @@ public abstract class AbstractUnitTest extends BaseTest { TABLE_8_PRIVILEGED_DTO.setDatabase(DATABASE_3_PRIVILEGED_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<>()); 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 4586f68d4f..f685e5fcb9 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 @@ -20,7 +20,6 @@ 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.query.QueryResultDto; import at.tuwien.api.database.table.TableBriefDto; import at.tuwien.api.database.table.TableCreateDto; import at.tuwien.api.database.table.TableDto; @@ -1417,11 +1416,11 @@ public abstract class BaseTest { public final static Long TABLE_1_ID = 1L; public final static String TABLE_1_NAME = "Weather AUS"; - public final static String TABLE_1_INTERNALNAME = "weather_aus"; + public final static String TABLE_1_INTERNAL_NAME = "weather_aus"; public final static Boolean TABLE_1_VERSIONED = true; public final static Boolean TABLE_1_PROCESSED_CONSTRAINTS = true; public final static String TABLE_1_DESCRIPTION = "Weather in Australia"; - public final static String TABLE_1_QUEUE_NAME = TABLE_1_INTERNALNAME; + public final static String TABLE_1_QUEUE_NAME = TABLE_1_INTERNAL_NAME; public final static String TABLE_1_ROUTING_KEY = "dbrepo\\." + DATABASE_1_ID + "\\." + TABLE_1_ID; public final static Long TABLE_1_DATABASE_ID = DATABASE_1_ID; public final static Long TABLE_1_AVG_ROW_LENGTH = 3L; @@ -1436,7 +1435,7 @@ public abstract class BaseTest { .tdbid(DATABASE_1_ID) .database(null) /* DATABASE_1_PRIVILEGED_DTO */ .created(TABLE_1_CREATED) - .internalName(TABLE_1_INTERNALNAME) + .internalName(TABLE_1_INTERNAL_NAME) .isVersioned(TABLE_1_VERSIONED) .description(TABLE_1_DESCRIPTION) .name(TABLE_1_NAME) @@ -1459,7 +1458,7 @@ public abstract class BaseTest { .tdbid(DATABASE_1_ID) .database(null /* DATABASE_1 */) .created(TABLE_1_CREATED) - .internalName(TABLE_1_INTERNALNAME) + .internalName(TABLE_1_INTERNAL_NAME) .isVersioned(TABLE_1_VERSIONED) .description(TABLE_1_DESCRIPTION) .name(TABLE_1_NAME) @@ -1482,7 +1481,7 @@ public abstract class BaseTest { .id(TABLE_1_ID) .tdbid(DATABASE_1_ID) .created(TABLE_1_CREATED) - .internalName(TABLE_1_INTERNALNAME) + .internalName(TABLE_1_INTERNAL_NAME) .isVersioned(TABLE_1_VERSIONED) .description(TABLE_1_DESCRIPTION) .name(TABLE_1_NAME) @@ -1574,7 +1573,7 @@ public abstract class BaseTest { public final static TableBriefDto TABLE_1_BRIEF_DTO = TableBriefDto.builder() .id(TABLE_1_ID) - .internalName(TABLE_1_INTERNALNAME) + .internalName(TABLE_1_INTERNAL_NAME) .isVersioned(TABLE_1_VERSIONED) .description(TABLE_1_DESCRIPTION) .name(TABLE_1_NAME) @@ -1582,38 +1581,29 @@ public abstract class BaseTest { .build(); public final static Long TABLE_1_DATA_COUNT = 3L; - public final static QueryResultDto TABLE_1_DATA_DTO = QueryResultDto.builder() - .headers(new LinkedList<>(List.of(new HashMap<>() {{ - put("id", 0); - put("date", 1); - put("location", 2); - put("mintemp", 3); - put("rainfall", 4); - }}))) - .result(new LinkedList<>(List.of( - new HashMap<>() {{ - put("id", BigInteger.valueOf(1L)); - put("date", LocalDate.of(2008, 12, 1).atStartOfDay().toInstant(ZoneOffset.UTC)); - put("location", "Albury"); - put("mintemp", 13.4); - put("rainfall", 0.6); - }}, - new HashMap<>() {{ - put("id", BigInteger.valueOf(2L)); - put("date", LocalDate.of(2008, 12, 2).atStartOfDay().toInstant(ZoneOffset.UTC)); - put("location", "Albury"); - put("mintemp", 7.4); - put("rainfall", 0); - }}, - new HashMap<>() {{ - put("id", BigInteger.valueOf(3L)); - put("date", LocalDate.of(2008, 12, 3).atStartOfDay().toInstant(ZoneOffset.UTC)); - put("location", "Albury"); - put("mintemp", 12.9); - put("rainfall", 0); - }} - ))) - .build(); + public final static List<Map<String, Object>> TABLE_1_DATA_DTO = new LinkedList<>(List.of( + new HashMap<>() {{ + put("id", BigInteger.valueOf(1L)); + put("date", LocalDate.of(2008, 12, 1).atStartOfDay().toInstant(ZoneOffset.UTC)); + put("location", "Albury"); + put("mintemp", 13.4); + put("rainfall", 0.6); + }}, + new HashMap<>() {{ + put("id", BigInteger.valueOf(2L)); + put("date", LocalDate.of(2008, 12, 2).atStartOfDay().toInstant(ZoneOffset.UTC)); + put("location", "Albury"); + put("mintemp", 7.4); + put("rainfall", 0); + }}, + new HashMap<>() {{ + put("id", BigInteger.valueOf(3L)); + put("date", LocalDate.of(2008, 12, 3).atStartOfDay().toInstant(ZoneOffset.UTC)); + put("location", "Albury"); + put("mintemp", 12.9); + put("rainfall", 0); + }} + )); public final static Long TABLE_2_ID = 2L; public final static String TABLE_2_NAME = "Weather Location"; @@ -2432,44 +2422,38 @@ public abstract class BaseTest { .build()); public final static Long TABLE_8_DATA_COUNT = 6L; - public final static QueryResultDto TABLE_8_DATA_DTO = QueryResultDto.builder() - .headers(new LinkedList<>(List.of(new HashMap<>() {{ - put(COLUMN_8_1_INTERNAL_NAME, 0); - put(COLUMN_8_2_INTERNAL_NAME, 1); - }}))) - .result(new LinkedList<>(List.of( - new HashMap<>() {{ - put(COLUMN_8_1_INTERNAL_NAME, BigInteger.valueOf(1L)); - put(COLUMN_8_2_INTERNAL_NAME, 11.2); - put(COLUMN_8_3_INTERNAL_NAME, null); - }}, - new HashMap<>() {{ - put(COLUMN_8_1_INTERNAL_NAME, BigInteger.valueOf(2L)); - put(COLUMN_8_2_INTERNAL_NAME, 11.3); - put(COLUMN_8_3_INTERNAL_NAME, null); - }}, - new HashMap<>() {{ - put(COLUMN_8_1_INTERNAL_NAME, BigInteger.valueOf(3L)); - put(COLUMN_8_2_INTERNAL_NAME, 11.4); - put(COLUMN_8_3_INTERNAL_NAME, null); - }}, - new HashMap<>() {{ - put(COLUMN_8_1_INTERNAL_NAME, BigInteger.valueOf(4L)); - put(COLUMN_8_2_INTERNAL_NAME, 11.9); - put(COLUMN_8_3_INTERNAL_NAME, null); - }}, - new HashMap<>() {{ - put(COLUMN_8_1_INTERNAL_NAME, BigInteger.valueOf(5L)); - put(COLUMN_8_2_INTERNAL_NAME, 12.3); - put(COLUMN_8_3_INTERNAL_NAME, null); - }}, - new HashMap<>() {{ - put(COLUMN_8_1_INTERNAL_NAME, BigInteger.valueOf(6L)); - put(COLUMN_8_2_INTERNAL_NAME, 23.1); - put(COLUMN_8_3_INTERNAL_NAME, null); - }} - ))) - .build(); + public final static List<Map<String, Object>> TABLE_8_DATA_DTO = new LinkedList<>(List.of( + new HashMap<>() {{ + put(COLUMN_8_1_INTERNAL_NAME, BigInteger.valueOf(1L)); + put(COLUMN_8_2_INTERNAL_NAME, 11.2); + put(COLUMN_8_3_INTERNAL_NAME, null); + }}, + new HashMap<>() {{ + put(COLUMN_8_1_INTERNAL_NAME, BigInteger.valueOf(2L)); + put(COLUMN_8_2_INTERNAL_NAME, 11.3); + put(COLUMN_8_3_INTERNAL_NAME, null); + }}, + new HashMap<>() {{ + put(COLUMN_8_1_INTERNAL_NAME, BigInteger.valueOf(3L)); + put(COLUMN_8_2_INTERNAL_NAME, 11.4); + put(COLUMN_8_3_INTERNAL_NAME, null); + }}, + new HashMap<>() {{ + put(COLUMN_8_1_INTERNAL_NAME, BigInteger.valueOf(4L)); + put(COLUMN_8_2_INTERNAL_NAME, 11.9); + put(COLUMN_8_3_INTERNAL_NAME, null); + }}, + new HashMap<>() {{ + put(COLUMN_8_1_INTERNAL_NAME, BigInteger.valueOf(5L)); + put(COLUMN_8_2_INTERNAL_NAME, 12.3); + put(COLUMN_8_3_INTERNAL_NAME, null); + }}, + new HashMap<>() {{ + put(COLUMN_8_1_INTERNAL_NAME, BigInteger.valueOf(6L)); + put(COLUMN_8_2_INTERNAL_NAME, 23.1); + put(COLUMN_8_3_INTERNAL_NAME, null); + }} + )); public final static TableStatisticDto TABLE_8_STATISTIC_DTO = TableStatisticDto.builder() .columns(new HashMap<>() {{ @@ -2637,11 +2621,6 @@ public abstract class BaseTest { put("value", 23.1); }}); - public final static QueryResultDto QUERY_4_RESULT_DTO = QueryResultDto.builder() - .id(QUERY_4_RESULT_ID) - .result(QUERY_4_RESULT_RESULT) - .build(); - public final static QueryDto QUERY_4_DTO = QueryDto.builder() .id(QUERY_4_ID) .databaseId(QUERY_4_DATABASE_ID) @@ -2687,20 +2666,14 @@ public abstract class BaseTest { .createdBy(QUERY_5_CREATED_BY) .build(); - public final static QueryResultDto QUERY_5_RESULT_DTO = QueryResultDto.builder() - .headers(new LinkedList<>(List.of(new HashMap<>() {{ - put("id", 0); - put("value", 1); - }}))) - .result(new LinkedList<>(List.of( - Map.of("id", BigInteger.valueOf(1L), "value", 11.2), - Map.of("id", BigInteger.valueOf(2L), "value", 11.3), - Map.of("id", BigInteger.valueOf(3L), "value", 11.4), - Map.of("id", BigInteger.valueOf(4L), "value", 11.9), - Map.of("id", BigInteger.valueOf(5L), "value", 12.3), - Map.of("id", BigInteger.valueOf(6L), "value", 23.1) - ))) - .build(); + public final static List<Map<String, Object>> QUERY_5_RESULT_DTO = new LinkedList<>(List.of( + Map.of("id", BigInteger.valueOf(1L), "value", 11.2), + Map.of("id", BigInteger.valueOf(2L), "value", 11.3), + Map.of("id", BigInteger.valueOf(3L), "value", 11.4), + Map.of("id", BigInteger.valueOf(4L), "value", 11.9), + Map.of("id", BigInteger.valueOf(5L), "value", 12.3), + Map.of("id", BigInteger.valueOf(6L), "value", 23.1) + )); public final static Long QUERY_6_ID = 6L; public final static String QUERY_6_STATEMENT = "SELECT `location` FROM `weather_aus` WHERE `id` = 1"; @@ -4721,30 +4694,23 @@ public abstract class BaseTest { .build(); public final static Long VIEW_1_DATA_COUNT = 3L; - public final static QueryResultDto VIEW_1_DATA_DTO = QueryResultDto.builder() - .headers(new LinkedList<>(List.of(new HashMap<>() {{ - put("location", 0); - put("lat", 1); - put("lng", 2); - }}))) - .result(new LinkedList<>(List.of( - new HashMap<>() {{ - put("location", "Albury"); - put("lat", -36.0653583); - put("lng", 146.9112214); - }}, - new HashMap<>() {{ - put("location", "Sydney"); - put("lat", -33.847927); - put("lng", 150.6517942); - }}, - new HashMap<>() {{ - put("location", "Vienna"); - put("lat", null); - put("lng", null); - }} - ))) - .build(); + public final static List<Map<String, Object>> VIEW_1_DATA_DTO = new LinkedList<>(List.of( + new HashMap<>() {{ + put("location", "Albury"); + put("lat", -36.0653583); + put("lng", 146.9112214); + }}, + new HashMap<>() {{ + put("location", "Sydney"); + put("lat", -33.847927); + put("lng", 150.6517942); + }}, + new HashMap<>() {{ + put("location", "Vienna"); + put("lat", null); + put("lng", null); + }} + )); public final static List<ViewColumn> VIEW_1_COLUMNS = List.of( ViewColumn.builder() @@ -5490,7 +5456,7 @@ public abstract class BaseTest { .query(VIEW_5_QUERY) .queryHash(VIEW_5_QUERY_HASH) .createdBy(USER_1_ID) - .columns(null) + .columns(new LinkedList<>()) .build(); public final static List<ViewColumn> VIEW_5_COLUMNS = List.of( @@ -5530,6 +5496,40 @@ public abstract class BaseTest { .view(VIEW_5) .build()); + public final static List<ViewColumnDto> VIEW_5_COLUMNS_DTO = List.of( + ViewColumnDto.builder() + .id(29L) + .ordinalPosition(0) + .name("location") + .internalName("location") + .ordinalPosition(0) + .columnType(ColumnTypeDto.VARCHAR) + .size(255L) + .isNullAllowed(false) + .build(), + ViewColumnDto.builder() + .id(30L) + .ordinalPosition(1) + .name("lat") + .internalName("lat") + .ordinalPosition(1) + .columnType(ColumnTypeDto.DECIMAL) + .size(10L) + .d(0L) + .isNullAllowed(true) + .build(), + ViewColumnDto.builder() + .id(31L) + .ordinalPosition(2) + .name("lng") + .internalName("lng") + .ordinalPosition(2) + .columnType(ColumnTypeDto.DECIMAL) + .size(10L) + .d(0L) + .isNullAllowed(true) + .build()); + public final static Long QUERY_1_RESULT_ID = 1L; public final static List<Map<String, Object>> QUERY_1_RESULT_RESULT = List.of( new HashMap<>() {{ @@ -5542,11 +5542,6 @@ public abstract class BaseTest { put("lng", 150.6517942); }}); - public final static QueryResultDto QUERY_1_RESULT_DTO = QueryResultDto.builder() - .id(QUERY_1_RESULT_ID) - .result(QUERY_1_RESULT_RESULT) - .build(); - public final static String LICENSE_1_IDENTIFIER = "MIT"; public final static String LICENSE_1_URI = "https://opensource.org/license/mit/"; diff --git a/dbrepo-search-service/Pipfile b/dbrepo-search-service/Pipfile index e081ac7bb3..da6b40dab3 100644 --- a/dbrepo-search-service/Pipfile +++ b/dbrepo-search-service/Pipfile @@ -18,7 +18,7 @@ jwt = "~=1.3" testcontainers-opensearch = "*" pytest = "*" rdflib = "*" -dbrepo = {path = "./lib/dbrepo-1.5.2.tar.gz"} +dbrepo = {path = "./lib/dbrepo-1.5.3.tar.gz"} gunicorn = "*" [dev-packages] diff --git a/dbrepo-search-service/Pipfile.lock b/dbrepo-search-service/Pipfile.lock index 9ccdcd7ec5..06ddaceeac 100644 --- a/dbrepo-search-service/Pipfile.lock +++ b/dbrepo-search-service/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "5f8f989adc3210e3d07474d3a6c1d5a8bc7352195b75ef07feaf135d9388c60c" + "sha256": "40b8d9185eb1297e855ab83a2b899ac611b661afbdf4dfb5c1fc97ca1bb61f23" }, "pipfile-spec": 6, "requires": { @@ -26,85 +26,85 @@ }, "aiohttp": { "hashes": [ - "sha256:0411777249f25d11bd2964a230b3ffafcbed6cd65d0f2b132bc2b8f5b8c347c7", - "sha256:0a97d657f6cf8782a830bb476c13f7d777cfcab8428ac49dde15c22babceb361", - "sha256:0b5a5009b0159a8f707879dc102b139466d8ec6db05103ec1520394fdd8ea02c", - "sha256:0bcb7f6976dc0b6b56efde13294862adf68dd48854111b422a336fa729a82ea6", - "sha256:14624d96f0d69cf451deed3173079a68c322279be6030208b045ab77e1e8d550", - "sha256:15c4e489942d987d5dac0ba39e5772dcbed4cc9ae3710d1025d5ba95e4a5349c", - "sha256:176f8bb8931da0613bb0ed16326d01330066bb1e172dd97e1e02b1c27383277b", - "sha256:17af09d963fa1acd7e4c280e9354aeafd9e3d47eaa4a6bfbd2171ad7da49f0c5", - "sha256:1a8b13b9950d8b2f8f58b6e5842c4b842b5887e2c32e3f4644d6642f1659a530", - "sha256:202f40fb686e5f93908eee0c75d1e6fbe50a43e9bd4909bf3bf4a56b560ca180", - "sha256:21cbe97839b009826a61b143d3ca4964c8590d7aed33d6118125e5b71691ca46", - "sha256:27935716f8d62c1c73010428db310fd10136002cfc6d52b0ba7bdfa752d26066", - "sha256:282e0a7ddd36ebc411f156aeaa0491e8fe7f030e2a95da532cf0c84b0b70bc66", - "sha256:28f29bce89c3b401a53d6fd4bee401ee943083bf2bdc12ef297c1d63155070b0", - "sha256:2ac9fd83096df36728da8e2f4488ac3b5602238f602706606f3702f07a13a409", - "sha256:30f9f89ae625d412043f12ca3771b2ccec227cc93b93bb1f994db6e1af40a7d3", - "sha256:317251b9c9a2f1a9ff9cd093775b34c6861d1d7df9439ce3d32a88c275c995cd", - "sha256:31de2f10f63f96cc19e04bd2df9549559beadd0b2ee2da24a17e7ed877ca8c60", - "sha256:36df00e0541f264ce42d62280281541a47474dfda500bc5b7f24f70a7f87be7a", - "sha256:39625703540feb50b6b7f938b3856d1f4886d2e585d88274e62b1bd273fae09b", - "sha256:3f5461c77649358610fb9694e790956b4238ac5d9e697a17f63619c096469afe", - "sha256:4313f3bc901255b22f01663eeeae167468264fdae0d32c25fc631d5d6e15b502", - "sha256:442356e8924fe1a121f8c87866b0ecdc785757fd28924b17c20493961b3d6697", - "sha256:44cb1a1326a0264480a789e6100dc3e07122eb8cd1ad6b784a3d47d13ed1d89c", - "sha256:44d323aa80a867cb6db6bebb4bbec677c6478e38128847f2c6b0f70eae984d72", - "sha256:499368eb904566fbdf1a3836a1532000ef1308f34a1bcbf36e6351904cced771", - "sha256:4b01d9cfcb616eeb6d40f02e66bebfe7b06d9f2ef81641fdd50b8dd981166e0b", - "sha256:5720ebbc7a1b46c33a42d489d25d36c64c419f52159485e55589fbec648ea49a", - "sha256:5cc5e0d069c56645446c45a4b5010d4b33ac6c5ebfd369a791b5f097e46a3c08", - "sha256:618b18c3a2360ac940a5503da14fa4f880c5b9bc315ec20a830357bcc62e6bae", - "sha256:6435a66957cdba1a0b16f368bde03ce9c79c57306b39510da6ae5312a1a5b2c1", - "sha256:647ec5bee7e4ec9f1034ab48173b5fa970d9a991e565549b965e93331f1328fe", - "sha256:6e1e9e447856e9b7b3d38e1316ae9a8c92e7536ef48373de758ea055edfd5db5", - "sha256:6ef1550bb5f55f71b97a6a395286db07f7f2c01c8890e613556df9a51da91e8d", - "sha256:6ffa45cc55b18d4ac1396d1ddb029f139b1d3480f1594130e62bceadf2e1a838", - "sha256:77f31cebd8c27a36af6c7346055ac564946e562080ee1a838da724585c67474f", - "sha256:7a3b5b2c012d70c63d9d13c57ed1603709a4d9d7d473e4a9dfece0e4ea3d5f51", - "sha256:7a7ddf981a0b953ade1c2379052d47ccda2f58ab678fca0671c7c7ca2f67aac2", - "sha256:84de955314aa5e8d469b00b14d6d714b008087a0222b0f743e7ffac34ef56aff", - "sha256:8dcfd14c712aa9dd18049280bfb2f95700ff6a8bde645e09f17c3ed3f05a0130", - "sha256:928f92f80e2e8d6567b87d3316c1fd9860ccfe36e87a9a7f5237d4cda8baa1ba", - "sha256:9384b07cfd3045b37b05ed002d1c255db02fb96506ad65f0f9b776b762a7572e", - "sha256:96726839a42429318017e67a42cca75d4f0d5248a809b3cc2e125445edd7d50d", - "sha256:96bbec47beb131bbf4bae05d8ef99ad9e5738f12717cfbbf16648b78b0232e87", - "sha256:9bcf97b971289be69638d8b1b616f7e557e1342debc7fc86cf89d3f08960e411", - "sha256:a0cf4d814689e58f57ecd5d8c523e6538417ca2e72ff52c007c64065cef50fb2", - "sha256:a7c6147c6306f537cff59409609508a1d2eff81199f0302dd456bb9e7ea50c39", - "sha256:a9266644064779840feec0e34f10a89b3ff1d2d6b751fe90017abcad1864fa7c", - "sha256:afbe85b50ade42ddff5669947afde9e8a610e64d2c80be046d67ec4368e555fa", - "sha256:afcda759a69c6a8be3aae764ec6733155aa4a5ad9aad4f398b52ba4037942fe3", - "sha256:b2fab23003c4bb2249729a7290a76c1dda38c438300fdf97d4e42bf78b19c810", - "sha256:bd3f711f4c99da0091ced41dccdc1bcf8be0281dc314d6d9c6b6cf5df66f37a9", - "sha256:be0c7c98e38a1e3ad7a6ff64af8b6d6db34bf5a41b1478e24c3c74d9e7f8ed42", - "sha256:c1f2d7fd583fc79c240094b3e7237d88493814d4b300d013a42726c35a734bc9", - "sha256:c5bba6b83fde4ca233cfda04cbd4685ab88696b0c8eaf76f7148969eab5e248a", - "sha256:c6beeac698671baa558e82fa160be9761cf0eb25861943f4689ecf9000f8ebd0", - "sha256:c7333e7239415076d1418dbfb7fa4df48f3a5b00f8fdf854fca549080455bc14", - "sha256:c8a02f74ae419e3955af60f570d83187423e42e672a6433c5e292f1d23619269", - "sha256:c9c23e62f3545c2216100603614f9e019e41b9403c47dd85b8e7e5015bf1bde0", - "sha256:cca505829cdab58c2495ff418c96092d225a1bbd486f79017f6de915580d3c44", - "sha256:d3108f0ad5c6b6d78eec5273219a5bbd884b4aacec17883ceefaac988850ce6e", - "sha256:d4b8a1b6c7a68c73191f2ebd3bf66f7ce02f9c374e309bdb68ba886bbbf1b938", - "sha256:d6e274661c74195708fc4380a4ef64298926c5a50bb10fbae3d01627d7a075b7", - "sha256:db2914de2559809fdbcf3e48f41b17a493b58cb7988d3e211f6b63126c55fe82", - "sha256:e738aabff3586091221044b7a584865ddc4d6120346d12e28e788307cd731043", - "sha256:e7f6173302f8a329ca5d1ee592af9e628d3ade87816e9958dcf7cdae2841def7", - "sha256:e9d036a9a41fc78e8a3f10a86c2fc1098fca8fab8715ba9eb999ce4788d35df0", - "sha256:ea142255d4901b03f89cb6a94411ecec117786a76fc9ab043af8f51dd50b5313", - "sha256:ebd3e6b0c7d4954cca59d241970011f8d3327633d555051c430bd09ff49dc494", - "sha256:ec656680fc53a13f849c71afd0c84a55c536206d524cbc831cde80abbe80489e", - "sha256:ec8df0ff5a911c6d21957a9182402aad7bf060eaeffd77c9ea1c16aecab5adbf", - "sha256:ed95d66745f53e129e935ad726167d3a6cb18c5d33df3165974d54742c373868", - "sha256:ef2c9499b7bd1e24e473dc1a85de55d72fd084eea3d8bdeec7ee0720decb54fa", - "sha256:f5252ba8b43906f206048fa569debf2cd0da0316e8d5b4d25abe53307f573941", - "sha256:f737fef6e117856400afee4f17774cdea392b28ecf058833f5eca368a18cf1bf", - "sha256:fc726c3fa8f606d07bd2b500e5dc4c0fd664c59be7788a16b9e34352c50b6b6b" + "sha256:012f176945af138abc10c4a48743327a92b4ca9adc7a0e078077cdb5dbab7be0", + "sha256:02c13415b5732fb6ee7ff64583a5e6ed1c57aa68f17d2bda79c04888dfdc2769", + "sha256:03b6002e20938fc6ee0918c81d9e776bebccc84690e2b03ed132331cca065ee5", + "sha256:04814571cb72d65a6899db6099e377ed00710bf2e3eafd2985166f2918beaf59", + "sha256:0580f2e12de2138f34debcd5d88894786453a76e98febaf3e8fe5db62d01c9bf", + "sha256:06a8e2ee1cbac16fe61e51e0b0c269400e781b13bcfc33f5425912391a542985", + "sha256:076bc454a7e6fd646bc82ea7f98296be0b1219b5e3ef8a488afbdd8e81fbac50", + "sha256:0c9527819b29cd2b9f52033e7fb9ff08073df49b4799c89cb5754624ecd98299", + "sha256:0dc49f42422163efb7e6f1df2636fe3db72713f6cd94688e339dbe33fe06d61d", + "sha256:14cdb5a9570be5a04eec2ace174a48ae85833c2aadc86de68f55541f66ce42ab", + "sha256:15fccaf62a4889527539ecb86834084ecf6e9ea70588efde86e8bc775e0e7542", + "sha256:24213ba85a419103e641e55c27dc7ff03536c4873470c2478cce3311ba1eee7b", + "sha256:31d5093d3acd02b31c649d3a69bb072d539d4c7659b87caa4f6d2bcf57c2fa2b", + "sha256:3691ed7726fef54e928fe26344d930c0c8575bc968c3e239c2e1a04bd8cf7838", + "sha256:386fbe79863eb564e9f3615b959e28b222259da0c48fd1be5929ac838bc65683", + "sha256:3bbbfff4c679c64e6e23cb213f57cc2c9165c9a65d63717108a644eb5a7398df", + "sha256:3de34936eb1a647aa919655ff8d38b618e9f6b7f250cc19a57a4bf7fd2062b6d", + "sha256:40d1c7a7f750b5648642586ba7206999650208dbe5afbcc5284bcec6579c9b91", + "sha256:44224d815853962f48fe124748227773acd9686eba6dc102578defd6fc99e8d9", + "sha256:47ad15a65fb41c570cd0ad9a9ff8012489e68176e7207ec7b82a0940dddfd8be", + "sha256:482cafb7dc886bebeb6c9ba7925e03591a62ab34298ee70d3dd47ba966370d2c", + "sha256:49c7dbbc1a559ae14fc48387a115b7d4bbc84b4a2c3b9299c31696953c2a5219", + "sha256:4b2c7ac59c5698a7a8207ba72d9e9c15b0fc484a560be0788b31312c2c5504e4", + "sha256:4cca22a61b7fe45da8fc73c3443150c3608750bbe27641fc7558ec5117b27fdf", + "sha256:4cfce37f31f20800a6a6620ce2cdd6737b82e42e06e6e9bd1b36f546feb3c44f", + "sha256:502a1464ccbc800b4b1995b302efaf426e8763fadf185e933c2931df7db9a199", + "sha256:53bf2097e05c2accc166c142a2090e4c6fd86581bde3fd9b2d3f9e93dda66ac1", + "sha256:593c114a2221444f30749cc5e5f4012488f56bd14de2af44fe23e1e9894a9c60", + "sha256:5d6958671b296febe7f5f859bea581a21c1d05430d1bbdcf2b393599b1cdce77", + "sha256:5ef359ebc6949e3a34c65ce20230fae70920714367c63afd80ea0c2702902ccf", + "sha256:613e5169f8ae77b1933e42e418a95931fb4867b2991fc311430b15901ed67079", + "sha256:61b9bae80ed1f338c42f57c16918853dc51775fb5cb61da70d590de14d8b5fb4", + "sha256:6362cc6c23c08d18ddbf0e8c4d5159b5df74fea1a5278ff4f2c79aed3f4e9f46", + "sha256:65a96e3e03300b41f261bbfd40dfdbf1c301e87eab7cd61c054b1f2e7c89b9e8", + "sha256:65e55ca7debae8faaffee0ebb4b47a51b4075f01e9b641c31e554fd376595c6c", + "sha256:68386d78743e6570f054fe7949d6cb37ef2b672b4d3405ce91fafa996f7d9b4d", + "sha256:68ff6f48b51bd78ea92b31079817aff539f6c8fc80b6b8d6ca347d7c02384e33", + "sha256:6ab29b8a0beb6f8eaf1e5049252cfe74adbaafd39ba91e10f18caeb0e99ffb34", + "sha256:77ae58586930ee6b2b6f696c82cf8e78c8016ec4795c53e36718365f6959dc82", + "sha256:77c4aa15a89847b9891abf97f3d4048f3c2d667e00f8a623c89ad2dccee6771b", + "sha256:78153314f26d5abef3239b4a9af20c229c6f3ecb97d4c1c01b22c4f87669820c", + "sha256:7852bbcb4d0d2f0c4d583f40c3bc750ee033265d80598d0f9cb6f372baa6b836", + "sha256:7e97d622cb083e86f18317282084bc9fbf261801b0192c34fe4b1febd9f7ae69", + "sha256:7f3dc0e330575f5b134918976a645e79adf333c0a1439dcf6899a80776c9ab39", + "sha256:80886dac673ceaef499de2f393fc80bb4481a129e6cb29e624a12e3296cc088f", + "sha256:811f23b3351ca532af598405db1093f018edf81368e689d1b508c57dcc6b6a32", + "sha256:86a5dfcc39309470bd7b68c591d84056d195428d5d2e0b5ccadfbaf25b026ebc", + "sha256:8b3cf2dc0f0690a33f2d2b2cb15db87a65f1c609f53c37e226f84edb08d10f52", + "sha256:8cc5203b817b748adccb07f36390feb730b1bc5f56683445bfe924fc270b8816", + "sha256:909af95a72cedbefe5596f0bdf3055740f96c1a4baa0dd11fd74ca4de0b4e3f1", + "sha256:974d3a2cce5fcfa32f06b13ccc8f20c6ad9c51802bb7f829eae8a1845c4019ec", + "sha256:98283b94cc0e11c73acaf1c9698dea80c830ca476492c0fe2622bd931f34b487", + "sha256:98f5635f7b74bcd4f6f72fcd85bea2154b323a9f05226a80bc7398d0c90763b0", + "sha256:99b7920e7165be5a9e9a3a7f1b680f06f68ff0d0328ff4079e5163990d046767", + "sha256:9bca390cb247dbfaec3c664326e034ef23882c3f3bfa5fbf0b56cad0320aaca5", + "sha256:9e2e576caec5c6a6b93f41626c9c02fc87cd91538b81a3670b2e04452a63def6", + "sha256:9ef405356ba989fb57f84cac66f7b0260772836191ccefbb987f414bcd2979d9", + "sha256:a55d2ad345684e7c3dd2c20d2f9572e9e1d5446d57200ff630e6ede7612e307f", + "sha256:ab7485222db0959a87fbe8125e233b5a6f01f4400785b36e8a7878170d8c3138", + "sha256:b1fc6b45010a8d0ff9e88f9f2418c6fd408c99c211257334aff41597ebece42e", + "sha256:b78f053a7ecfc35f0451d961dacdc671f4bcbc2f58241a7c820e9d82559844cf", + "sha256:b99acd4730ad1b196bfb03ee0803e4adac371ae8efa7e1cbc820200fc5ded109", + "sha256:be2b516f56ea883a3e14dda17059716593526e10fb6303189aaf5503937db408", + "sha256:beb39a6d60a709ae3fb3516a1581777e7e8b76933bb88c8f4420d875bb0267c6", + "sha256:bf3d1a519a324af764a46da4115bdbd566b3c73fb793ffb97f9111dbc684fc4d", + "sha256:c49a76c1038c2dd116fa443eba26bbb8e6c37e924e2513574856de3b6516be99", + "sha256:c5532f0441fc09c119e1dca18fbc0687e64fbeb45aa4d6a87211ceaee50a74c4", + "sha256:c6b9e6d7e41656d78e37ce754813fa44b455c3d0d0dced2a047def7dc5570b74", + "sha256:c87bf31b7fdab94ae3adbe4a48e711bfc5f89d21cf4c197e75561def39e223bc", + "sha256:cbad88a61fa743c5d283ad501b01c153820734118b65aee2bd7dbb735475ce0d", + "sha256:cf14627232dfa8730453752e9cdc210966490992234d77ff90bc8dc0dce361d5", + "sha256:db1d0b28fcb7f1d35600150c3e4b490775251dea70f894bf15c678fdd84eda6a", + "sha256:ddf5f7d877615f6a1e75971bfa5ac88609af3b74796ff3e06879e8422729fd01", + "sha256:e44a9a3c053b90c6f09b1bb4edd880959f5328cf63052503f892c41ea786d99f", + "sha256:efb15a17a12497685304b2d976cb4939e55137df7b09fa53f1b6a023f01fcb4e", + "sha256:fbbaea811a2bba171197b08eea288b9402faa2bab2ba0858eecdd0a4105753a3" ], "markers": "python_version >= '3.9'", - "version": "==3.11.9" + "version": "==3.11.10" }, "aiosignal": { "hashes": [ @@ -345,7 +345,6 @@ "sha256:3c672a53c0fb4725a29c303be906d3c1fa99c32f58abe008a82705f9ee96f40b", "sha256:404fdc66ee5f83a1388be54300ae978b2efd538018de18556dde92575e05defc", "sha256:4ac4c9f37eba52cb6fbeaf5b59c152ea976726b865bd4cf87883a7e7006cc543", - "sha256:60eb32934076fa07e4316b7b2742fa52cbb190b42c2df2863dbc4230a0a9b385", "sha256:62901fb618f74d7d81bf408c8719e9ec14d863086efe4185afd07c352aee1d2c", "sha256:660cb7312a08bc38be15b696462fa7cc7cd85c3ed9c576e81f4dc4d8b2b31591", "sha256:708ee5f1bafe76d041b53a4f95eb28cdeb8d18da17e597d46d7833ee59b97ede", @@ -353,7 +352,6 @@ "sha256:831c3c4d0774e488fdc83a1923b49b9957d33287de923d58ebd3cec47a0ae43f", "sha256:84111ad4ff3f6253820e6d3e58be2cc2a00adb29335d4cacb5ab4d4d34f2a123", "sha256:8b3e6eae66cf54701ee7d9c83c30ac0a1e3fa17be486033000f2a73a12ab507c", - "sha256:9abcc2e083cbe8dde89124a47e5e53ec38751f0d7dfd36801008f316a127d7ba", "sha256:9e6fc8a08e116fb7c7dd1f040074c9d7b51d74a8ea40d4df2fc7aa08b76b9e6c", "sha256:a01956ddfa0a6790d594f5b34fc1bfa6098aca434696a03cfdbe469b8ed79285", "sha256:abc998e0c0eee3c8a1904221d3f67dcfa76422b23620173e28c11d3e626c21bd", @@ -375,9 +373,9 @@ }, "dbrepo": { "hashes": [ - "sha256:292c2631816ca3dbdbd243e4c006c4bd39d512f5ae7e4b10070102c85ec58a10" + "sha256:b746668ca7830897aa9ab7660c2b6cf420a6cde09afb87665f9fa9cb4d6a2314" ], - "path": "./lib/dbrepo-1.5.2.tar.gz" + "path": "./lib/dbrepo-1.5.3.tar.gz" }, "docker": { "hashes": [ @@ -860,64 +858,64 @@ }, "numpy": { "hashes": [ - "sha256:016d0f6f5e77b0f0d45d77387ffa4bb89816b57c835580c3ce8e099ef830befe", - "sha256:02135ade8b8a84011cbb67dc44e07c58f28575cf9ecf8ab304e51c05528c19f0", - "sha256:08788d27a5fd867a663f6fc753fd7c3ad7e92747efc73c53bca2f19f8bc06f48", - "sha256:0d30c543f02e84e92c4b1f415b7c6b5326cbe45ee7882b6b77db7195fb971e3a", - "sha256:0fa14563cc46422e99daef53d725d0c326e99e468a9320a240affffe87852564", - "sha256:13138eadd4f4da03074851a698ffa7e405f41a0845a6b1ad135b81596e4e9958", - "sha256:14e253bd43fc6b37af4921b10f6add6925878a42a0c5fe83daee390bca80bc17", - "sha256:15cb89f39fa6d0bdfb600ea24b250e5f1a3df23f901f51c8debaa6a5d122b2f0", - "sha256:17ee83a1f4fef3c94d16dc1802b998668b5419362c8a4f4e8a491de1b41cc3ee", - "sha256:2312b2aa89e1f43ecea6da6ea9a810d06aae08321609d8dc0d0eda6d946a541b", - "sha256:2564fbdf2b99b3f815f2107c1bbc93e2de8ee655a69c261363a1172a79a257d4", - "sha256:3522b0dfe983a575e6a9ab3a4a4dfe156c3e428468ff08ce582b9bb6bd1d71d4", - "sha256:4394bc0dbd074b7f9b52024832d16e019decebf86caf909d94f6b3f77a8ee3b6", - "sha256:45966d859916ad02b779706bb43b954281db43e185015df6eb3323120188f9e4", - "sha256:4d1167c53b93f1f5d8a139a742b3c6f4d429b54e74e6b57d0eff40045187b15d", - "sha256:4f2015dfe437dfebbfce7c85c7b53d81ba49e71ba7eadbf1df40c915af75979f", - "sha256:50ca6aba6e163363f132b5c101ba078b8cbd3fa92c7865fd7d4d62d9779ac29f", - "sha256:50d18c4358a0a8a53f12a8ba9d772ab2d460321e6a93d6064fc22443d189853f", - "sha256:5641516794ca9e5f8a4d17bb45446998c6554704d888f86df9b200e66bdcce56", - "sha256:576a1c1d25e9e02ed7fa5477f30a127fe56debd53b8d2c89d5578f9857d03ca9", - "sha256:6a4825252fcc430a182ac4dee5a505053d262c807f8a924603d411f6718b88fd", - "sha256:72dcc4a35a8515d83e76b58fdf8113a5c969ccd505c8a946759b24e3182d1f23", - "sha256:747641635d3d44bcb380d950679462fae44f54b131be347d5ec2bce47d3df9ed", - "sha256:762479be47a4863e261a840e8e01608d124ee1361e48b96916f38b119cfda04a", - "sha256:78574ac2d1a4a02421f25da9559850d59457bac82f2b8d7a44fe83a64f770098", - "sha256:825656d0743699c529c5943554d223c021ff0494ff1442152ce887ef4f7561a1", - "sha256:8637dcd2caa676e475503d1f8fdb327bc495554e10838019651b76d17b98e512", - "sha256:96fe52fcdb9345b7cd82ecd34547fca4321f7656d500eca497eb7ea5a926692f", - "sha256:973faafebaae4c0aaa1a1ca1ce02434554d67e628b8d805e61f874b84e136b09", - "sha256:996bb9399059c5b82f76b53ff8bb686069c05acc94656bb259b1d63d04a9506f", - "sha256:a38c19106902bb19351b83802531fea19dee18e5b37b36454f27f11ff956f7fc", - "sha256:a6b46587b14b888e95e4a24d7b13ae91fa22386c199ee7b418f449032b2fa3b8", - "sha256:a9f7f672a3388133335589cfca93ed468509cb7b93ba3105fce780d04a6576a0", - "sha256:aa08e04e08aaf974d4458def539dece0d28146d866a39da5639596f4921fd761", - "sha256:b0df3635b9c8ef48bd3be5f862cf71b0a4716fa0e702155c45067c6b711ddcef", - "sha256:b47fbb433d3260adcd51eb54f92a2ffbc90a4595f8970ee00e064c644ac788f5", - "sha256:baed7e8d7481bfe0874b566850cb0b85243e982388b7b23348c6db2ee2b2ae8e", - "sha256:bc6f24b3d1ecc1eebfbf5d6051faa49af40b03be1aaa781ebdadcbc090b4539b", - "sha256:c006b607a865b07cd981ccb218a04fc86b600411d83d6fc261357f1c0966755d", - "sha256:c181ba05ce8299c7aa3125c27b9c2167bca4a4445b7ce73d5febc411ca692e43", - "sha256:c7662f0e3673fe4e832fe07b65c50342ea27d989f92c80355658c7f888fcc83c", - "sha256:c80e4a09b3d95b4e1cac08643f1152fa71a0a821a2d4277334c88d54b2219a41", - "sha256:c894b4305373b9c5576d7a12b473702afdf48ce5369c074ba304cc5ad8730dff", - "sha256:d7aac50327da5d208db2eec22eb11e491e3fe13d22653dce51b0f4109101b408", - "sha256:d89dd2b6da69c4fff5e39c28a382199ddedc3a5be5390115608345dec660b9e2", - "sha256:d9beb777a78c331580705326d2367488d5bc473b49a9bc3036c154832520aca9", - "sha256:dc258a761a16daa791081d026f0ed4399b582712e6fc887a95af09df10c5ca57", - "sha256:e14e26956e6f1696070788252dcdff11b4aca4c3e8bd166e0df1bb8f315a67cb", - "sha256:e6988e90fcf617da2b5c78902fe8e668361b43b4fe26dbf2d7b0f8034d4cafb9", - "sha256:e711e02f49e176a01d0349d82cb5f05ba4db7d5e7e0defd026328e5cfb3226d3", - "sha256:ea4dedd6e394a9c180b33c2c872b92f7ce0f8e7ad93e9585312b0c5a04777a4a", - "sha256:ecc76a9ba2911d8d37ac01de72834d8849e55473457558e12995f4cd53e778e0", - "sha256:f55ba01150f52b1027829b50d70ef1dafd9821ea82905b63936668403c3b471e", - "sha256:f653490b33e9c3a4c1c01d41bc2aef08f9475af51146e4a7710c450cf9761598", - "sha256:fa2d1337dc61c8dc417fbccf20f6d1e139896a30721b7f1e832b2bb6ef4eb6c4" + "sha256:0557eebc699c1c34cccdd8c3778c9294e8196df27d713706895edc6f57d29608", + "sha256:0798b138c291d792f8ea40fe3768610f3c7dd2574389e37c3f26573757c8f7ef", + "sha256:0da8495970f6b101ddd0c38ace92edea30e7e12b9a926b57f5fabb1ecc25bb90", + "sha256:0f0986e917aca18f7a567b812ef7ca9391288e2acb7a4308aa9d265bd724bdae", + "sha256:122fd2fcfafdefc889c64ad99c228d5a1f9692c3a83f56c292618a59aa60ae83", + "sha256:140dd80ff8981a583a60980be1a655068f8adebf7a45a06a6858c873fcdcd4a0", + "sha256:16757cf28621e43e252c560d25b15f18a2f11da94fea344bf26c599b9cf54b73", + "sha256:18142b497d70a34b01642b9feabb70156311b326fdddd875a9981f34a369b671", + "sha256:1c92113619f7b272838b8d6702a7f8ebe5edea0df48166c47929611d0b4dea69", + "sha256:1e25507d85da11ff5066269d0bd25d06e0a0f2e908415534f3e603d2a78e4ffa", + "sha256:30bf971c12e4365153afb31fc73f441d4da157153f3400b82db32d04de1e4066", + "sha256:3579eaeb5e07f3ded59298ce22b65f877a86ba8e9fe701f5576c99bb17c283da", + "sha256:36b2b43146f646642b425dd2027730f99bac962618ec2052932157e213a040e9", + "sha256:3905a5fffcc23e597ee4d9fb3fcd209bd658c352657548db7316e810ca80458e", + "sha256:3a4199f519e57d517ebd48cb76b36c82da0360781c6a0353e64c0cac30ecaad3", + "sha256:3f2f5cddeaa4424a0a118924b988746db6ffa8565e5829b1841a8a3bd73eb59a", + "sha256:40deb10198bbaa531509aad0cd2f9fadb26c8b94070831e2208e7df543562b74", + "sha256:440cfb3db4c5029775803794f8638fbdbf71ec702caf32735f53b008e1eaece3", + "sha256:4723a50e1523e1de4fccd1b9a6dcea750c2102461e9a02b2ac55ffeae09a4410", + "sha256:4bddbaa30d78c86329b26bd6aaaea06b1e47444da99eddac7bf1e2fab717bd72", + "sha256:4e58666988605e251d42c2818c7d3d8991555381be26399303053b58a5bbf30d", + "sha256:54dc1d6d66f8d37843ed281773c7174f03bf7ad826523f73435deb88ba60d2d4", + "sha256:57fcc997ffc0bef234b8875a54d4058afa92b0b0c4223fc1f62f24b3b5e86038", + "sha256:58b92a5828bd4d9aa0952492b7de803135038de47343b2aa3cc23f3b71a3dc4e", + "sha256:5a145e956b374e72ad1dff82779177d4a3c62bc8248f41b80cb5122e68f22d13", + "sha256:6ab153263a7c5ccaf6dfe7e53447b74f77789f28ecb278c3b5d49db7ece10d6d", + "sha256:7832f9e8eb00be32f15fdfb9a981d6955ea9adc8574c521d48710171b6c55e95", + "sha256:7fe4bb0695fe986a9e4deec3b6857003b4cfe5c5e4aac0b95f6a658c14635e31", + "sha256:7fe8f3583e0607ad4e43a954e35c1748b553bfe9fdac8635c02058023277d1b3", + "sha256:85ad7d11b309bd132d74397fcf2920933c9d1dc865487128f5c03d580f2c3d03", + "sha256:9874bc2ff574c40ab7a5cbb7464bf9b045d617e36754a7bc93f933d52bd9ffc6", + "sha256:a184288538e6ad699cbe6b24859206e38ce5fba28f3bcfa51c90d0502c1582b2", + "sha256:a222d764352c773aa5ebde02dd84dba3279c81c6db2e482d62a3fa54e5ece69b", + "sha256:a50aeff71d0f97b6450d33940c7181b08be1441c6c193e678211bff11aa725e7", + "sha256:a55dc7a7f0b6198b07ec0cd445fbb98b05234e8b00c5ac4874a63372ba98d4ab", + "sha256:a62eb442011776e4036af5c8b1a00b706c5bc02dc15eb5344b0c750428c94219", + "sha256:a7d41d1612c1a82b64697e894b75db6758d4f21c3ec069d841e60ebe54b5b571", + "sha256:a98f6f20465e7618c83252c02041517bd2f7ea29be5378f09667a8f654a5918d", + "sha256:afe8fb968743d40435c3827632fd36c5fbde633b0423da7692e426529b1759b1", + "sha256:b0b227dcff8cdc3efbce66d4e50891f04d0a387cce282fe1e66199146a6a8fca", + "sha256:b30042fe92dbd79f1ba7f6898fada10bdaad1847c44f2dff9a16147e00a93661", + "sha256:b606b1aaf802e6468c2608c65ff7ece53eae1a6874b3765f69b8ceb20c5fa78e", + "sha256:b6207dc8fb3c8cb5668e885cef9ec7f70189bec4e276f0ff70d5aa078d32c88e", + "sha256:c2aed8fcf8abc3020d6a9ccb31dbc9e7d7819c56a348cc88fd44be269b37427e", + "sha256:cb24cca1968b21355cc6f3da1a20cd1cebd8a023e3c5b09b432444617949085a", + "sha256:cff210198bb4cae3f3c100444c5eaa573a823f05c253e7188e1362a5555235b3", + "sha256:d35717333b39d1b6bb8433fa758a55f1081543de527171543a2b710551d40881", + "sha256:df12a1f99b99f569a7c2ae59aa2d31724e8d835fc7f33e14f4792e3071d11221", + "sha256:e09d40edfdb4e260cb1567d8ae770ccf3b8b7e9f0d9b5c2a9992696b30ce2742", + "sha256:e12c6c1ce84628c52d6367863773f7c8c8241be554e8b79686e91a43f1733773", + "sha256:e2b8cd48a9942ed3f85b95ca4105c45758438c7ed28fff1e4ce3e57c3b589d8e", + "sha256:e500aba968a48e9019e42c0c199b7ec0696a97fa69037bea163b55398e390529", + "sha256:ebe5e59545401fbb1b24da76f006ab19734ae71e703cdb4a8b347e84a0cece67", + "sha256:f0dd071b95bbca244f4cb7f70b77d2ff3aaaba7fa16dc41f58d14854a6204e6c", + "sha256:f8c8b141ef9699ae777c6278b52c706b653bf15d135d302754f6b2e90eb30367" ], "markers": "python_version == '3.11'", - "version": "==2.1.3" + "version": "==2.2.0" }, "opensearch-py": { "hashes": [ @@ -1361,120 +1359,120 @@ }, "rpds-py": { "hashes": [ - "sha256:0545928bdf53dfdfcab284468212efefb8a6608ca3b6910c7fb2e5ed8bdc2dc0", - "sha256:05fdeae9010533e47715c37df83264df0122584e40d691d50cf3607c060952a3", - "sha256:09a1f000c5f6e08b298275bae00921e9fbbf2a35dae0a86db2821c058c2201a9", - "sha256:0a53592cdf98cec3dfcdb24ffec8a4797e7656b65700099af43ec7df023b6de4", - "sha256:0f057a0c546c42964836b209d8de9ea1a4f4b0432006c6343cbe633d8ca14571", - "sha256:0f9eb37d3a60b262a98ab51ee899cac039de9ca0ce68dcf1a6518a09719020b0", - "sha256:102be79c4cc47a4aeb5912401185c404cd2601c15a7163bbecff7f1bfe20b669", - "sha256:128cbaed7ba26116820bcb992405d6a13ea18c8fca1b8c4f59906d858e91e979", - "sha256:149b4d875ef9b12a8f5e303e86a32a58f8ef627e57ec97a7d0e4be819069d141", - "sha256:153248f48d6f90a295a502f53ec544a3ffbd21b0bb32f5dca39c4b93a764d6a2", - "sha256:157a023bded0618a1eea54979fe2e0f9309e9ddc818ef4b8fc3b884ff38fedd5", - "sha256:15fa4ca658f8ad22645d3531682b17e5580832efbfa87304c3e62214c79c1e8a", - "sha256:198067aa6f3d942ff5d0d655bb1e91b59ae85279d47590682cba2834ac1b97d2", - "sha256:1c40e02cc4f3e18fd39344edb10eebe04bd11cfd13119606b5771e5ea51630d3", - "sha256:1ded65691a1d3fd7d2aa89d2c91aa51f941601bb2ce099739909034d957fef4b", - "sha256:201650b309c419143775c15209c620627de3c09a27c7fb58375325aec5cce260", - "sha256:2143c3aed85992604d758bbe67da839fb4aab3dd2e1c6dddab5b3ca7162b34a2", - "sha256:2177e59c033bf0d1bf7de1ced561205963583caf3242c6c700a723034bfb5f8e", - "sha256:2ea23f1525d4f64286dbe0947c929d45c3ffe963b2dbed1d3844a2e4938bda42", - "sha256:31264187fc934ff1024a4f56775f33c9252d3f4f3e27ec07d1995a26b52702c3", - "sha256:36ce951800ed2acc6772fd9f42150f29d567f0423989748052fdb39d9e2b5795", - "sha256:3aaa22487477de9618ce3b37f99fbe81219ba96f3c2ca84f576f0ab451b83aba", - "sha256:3e7e99e2af59c56c59b6c964d612511b8203480d39d1ef83edc56f2cb42a3f5d", - "sha256:413a30a99d8683dace3765885920ed27ab662efbb6c98d81db76c397ad1ffd71", - "sha256:447ae1104fb32197b9262f772d565d38e834cc2e9edd89350b37b88fed636e70", - "sha256:4659b2e4a5008715099e216050f5c6976e5a4329482664411789968b82e3f17d", - "sha256:48ee97c7c6027fd423058675b5a39d0b5f7a1648250b671563d5c9f74ff13ff0", - "sha256:4ba6c66fbc6015b2f99e7176fec41793cecb00c4cc357cad038dff85e6ac42ab", - "sha256:4c8dc7331e8cbb1c0ea2bcb550adb1777365944ffd125c69aa1117fdef4887f5", - "sha256:50e4b5d291105f7063259fe0125b1af902fb34499444d7c5c521dd8328b00939", - "sha256:542eb246d5be31b5e0a9c8ddb9539416f9b31f58f75bd4ee328bff2b5c58d6fd", - "sha256:55d371b9d8b0c2a68a50413a8cb01c3c3ce1ea4f768bf77b66669a9a486e101e", - "sha256:580ccbf11f02f948add4cb641843030a89f1463d7c0740cbfc9aca91e9dc34b3", - "sha256:5dbff9402c2bdf00bf0df9905694b3c292a3847c725651938a72f554351a5fcb", - "sha256:5f941fb86195f97be7f6efe04a21b223f05dfe4d1dfb159999e2f8d101e44cc4", - "sha256:608c84699b2db09c6a8743845b1a3dad36fae53eaaecb241d45b13dff74405fb", - "sha256:626b9feb01bff049a5aec4804f0c58db12585778b4902e5376a95b01f80a7a16", - "sha256:66f4f48a89cdd30ab3a47335df81c76e9a63799d0d84b29c0618371c66fa37b0", - "sha256:6c8e97e19aa7b0b0d801a159f932ce4435f1049c8c38e2bb372bb5bee559ce50", - "sha256:72407065ad459db9f3d052ea8c51e02534f02533fc61e51cbab3bd94166f086c", - "sha256:734783dd7da58f76222f458346ddebdb3621686a1a2a667db5049caf0c9956b9", - "sha256:76eaa4c087a061a2c8a0a92536405069878a8f530c00e84a9eaf332e70f5561f", - "sha256:776a06cb5720556a549829896a49acebb5bdd96c7bba100191a994053546975a", - "sha256:7839b7528faa4d134c183b1f2dd1ee4dc2ca2f899f4f0cfdf00fc04c255262a7", - "sha256:8080467df22feca0fc9c46567001777c6fbc2b4a2683a7137420896051874ca1", - "sha256:85060e96953647871957d41707adb8d7bff4e977042fd0deb4fc1881b98dd2fe", - "sha256:8954b9ffe60f479a0c0ba40987db2546c735ab02a725ea7fd89342152d4d821d", - "sha256:8a603155db408f773637f9e3a712c6e3cbc521aaa8fa2b99f9ba6106c59a2496", - "sha256:8bd9ec1db79a664f4cbb12878693b73416f4d2cb425d3e27eccc1bdfbdc826ef", - "sha256:8c0c324879d483504b07f7b18eb1b50567c434263bbe4866ecce33056162668a", - "sha256:8ce729f1dc8a4a190c34b69f75377bddc004079b2963ab722ab91fafe040be6d", - "sha256:8ec41049c90d204a6561238a9ad6c7263ebb7009d9759c98b58078d9d2fec9ba", - "sha256:959ae04ed30cde606f3a0320f0a1f4167a107e685ef5209cce28c5080590bd31", - "sha256:96559e05bdf938b2048353e10a7920b98f853cefe4482c2064a718d7d0a50bd7", - "sha256:96b3759d8ab2323324e0a92b2f44834f9d88089b8d1ab6f533b61f4be3411cef", - "sha256:97c5ffe47ccf92d8b17e10f8a5ce28d015aa1196edc3359684cf31504eae6a14", - "sha256:9d5b925156a746dc1f5f52376fdd1fbdd3f6ffe1fcd6f5e06f77ca79abb940a3", - "sha256:9dae4eb9b5534e09ba6c6ab496a757e5e394b7e7b08767d25ca37e8d36491114", - "sha256:a083221b6a4ecdef38a60c95d8d3223d99449cb4da2544e9644958dc16664eb9", - "sha256:a0ed14a4162c2c2b21a162c9fcf90057e3e7da18cd171ab344c1e1664f75090e", - "sha256:a18aedc032d6468b73ebbe4437129cb30d54fe543cde2f23671ecad76c3aea24", - "sha256:a451dba533be77454ebcffc85189108fc05f279100835ac76e7989edacb89156", - "sha256:aa2ba0176037c915d8660a4e46581d645e2c22b5373e466bc8640a794d45861a", - "sha256:ab27dd4edd84b13309f268ffcdfc07aef8339135ffab7b6d43f16884307a2a48", - "sha256:ab784621d3e2a41916e21f13a483602cc989fd45fff637634b9231ba43d4383b", - "sha256:b07fa9e634234e84096adfa4be3828c8f26e238679c122824b2b3d7131bec578", - "sha256:b09209cdfcacf5eba9cf80367130532e6c02e695252e1f64d3cfcc2356e6e19f", - "sha256:babec324e8654a59122aaa66936a9a483faa03276db9792f51332475c2dddc4a", - "sha256:bca4428c4a957b78ded3e6e62884ab03f029dce8fa8d34818da0f80f61332b49", - "sha256:c0467838c90435b80793cde486a318fc916ee57f2af54e4b10c72b20cbdcbaa9", - "sha256:c2a214bf5b79bd39a9de1c991353aaaacafda83ba1374178309e92be8e67d411", - "sha256:c3029f481b31f329b1fdb4ec4b56935d82210ddd9c6f86ea5a87c06f1e97b161", - "sha256:c6f3fd617db422c9d4e12cb8d84c984fe07d6d9cb0950cbf117f3bccc6268d05", - "sha256:c783e4ed68200f4e03c125690d23158b1c49c4b186d458a18debc109bbdc3c2e", - "sha256:c8502a02ae3ae67084f5a0bf5a8253b19fa7a887f824e41e016cdb0ac532a06f", - "sha256:c88535f83f7391cf3a45af990237e3939a6fdfbedaed2571633bfdd0bceb36b0", - "sha256:c9ce6b83597d45bec44a2690857ede62fc98223772135f8a7fa90884eb726501", - "sha256:ca4657e9fd0b1b5376942d403d634ce188f79064f0873aa853ab05b10185ceec", - "sha256:d0ff8d5b13ce2357fa8b33a0a2e3775aa71df5bf7c8ba060634c9d15ab12f357", - "sha256:d280b4bf09f719b89fd9aab3b71067acc0d0449b7d1eba99a2ade4939cef8296", - "sha256:d3777c446bb1c5fcd82dc3f8776e1a146cd91e80cc1892f8634575ace438d22f", - "sha256:d7833ef6f5d6cb634f296abfd93452fb3eb44c4e9a6ae95c1021eab704c1cee2", - "sha256:d8306f27418361b788e3fca9f47dec125457f80122e7e31ba7ff5cdba98343f8", - "sha256:d962e2e89b3a95e3597a34b8c93ced1e98958502c5b8096c9fd69deff279f561", - "sha256:dbe428d0ac6eacaf05402adbaf137f59ad6063848182d1ff294f95ce0f24005b", - "sha256:e4f91d702b9ce1388660b3d4a28aa552614a1399e93f718ed0dacd68f23b3d32", - "sha256:e69acdbc132c9592c8dc393af85e38e206ca847c7019a953ff625191c3a12312", - "sha256:e8056adcefa2dcb67e8bc91ea5eee26df66e8b297a8cd6ff0903f85c70908fa0", - "sha256:e9ac7280bd045f472b50306d7efeee051b69e3a2dd1b90f46bd7e86e63b1efa2", - "sha256:eb013aa01b404219f28dc973d9e6310fd4db216d7299253dd355629952e0564e", - "sha256:ec1ccc2a9f764cd632fb8ab28fdde166250df54fc8d97315a4a6948dc5367639", - "sha256:ef7282d8a14b60dd515e47060638687710b1d518f4b5e961caad43fb3a3606f9", - "sha256:ef92b1fbe6aa2e7885eb90853cc016b1fc95439a8cc8da6d526880e9e2148695", - "sha256:efb2ad60ca8637d5f9f653f9a9a8d73964059972b6b95036be77e028bffc68a3", - "sha256:effcae2152afe7937a28376dbabb25c770ef99ed4e16a4ffeb8e6a4f7c4f06aa", - "sha256:f2d1b58a0c3a73f0361759642e80260a6d28eee6501b40fe25b82af33ef83f21", - "sha256:f57e2d0f8022783426121b586d7c842ea40ea832a29e28ca36c881b54c74fb28", - "sha256:f5cae9b415ea8a6a563566dbf46650222eccc5971c7daa16fbee63aef92ae543", - "sha256:f76c6f319e57007ad52e671ec741d801324760a377e3d4992c9bb8200333ebac", - "sha256:f91bfc39f7a64168e08ab831fa497ec5438c1d6c6e2f9e12848d95ad11ac8523", - "sha256:fdaee3947eaaa52dae3ceb9d9f66329e13d8bae35682b1e5dd54612938693934", - "sha256:fe3f245c2f39a5692d9123c174bc48f6f9fe3e96407e67c6d04541a767d99e72", - "sha256:ffae97d28ea4f2c613a751d087b75a97fb78311b38cc2e9a2f4587e473ace167" + "sha256:009de23c9c9ee54bf11303a966edf4d9087cd43a6003672e6aa7def643d06518", + "sha256:02fbb9c288ae08bcb34fb41d516d5eeb0455ac35b5512d03181d755d80810059", + "sha256:0a0461200769ab3b9ab7e513f6013b7a97fdeee41c29b9db343f3c5a8e2b9e61", + "sha256:0b09865a9abc0ddff4e50b5ef65467cd94176bf1e0004184eb915cbc10fc05c5", + "sha256:0b8db6b5b2d4491ad5b6bdc2bc7c017eec108acbf4e6785f42a9eb0ba234f4c9", + "sha256:0c150c7a61ed4a4f4955a96626574e9baf1adf772c2fb61ef6a5027e52803543", + "sha256:0f3cec041684de9a4684b1572fe28c7267410e02450f4561700ca5a3bc6695a2", + "sha256:1352ae4f7c717ae8cba93421a63373e582d19d55d2ee2cbb184344c82d2ae55a", + "sha256:177c7c0fce2855833819c98e43c262007f42ce86651ffbb84f37883308cb0e7d", + "sha256:1978d0021e943aae58b9b0b196fb4895a25cc53d3956b8e35e0b7682eefb6d56", + "sha256:1a60bce91f81ddaac922a40bbb571a12c1070cb20ebd6d49c48e0b101d87300d", + "sha256:1aef18820ef3e4587ebe8b3bc9ba6e55892a6d7b93bac6d29d9f631a3b4befbd", + "sha256:1e9663daaf7a63ceccbbb8e3808fe90415b0757e2abddbfc2e06c857bf8c5e2b", + "sha256:20070c65396f7373f5df4005862fa162db5d25d56150bddd0b3e8214e8ef45b4", + "sha256:214b7a953d73b5e87f0ebece4a32a5bd83c60a3ecc9d4ec8f1dca968a2d91e99", + "sha256:22bebe05a9ffc70ebfa127efbc429bc26ec9e9b4ee4d15a740033efda515cf3d", + "sha256:24e8abb5878e250f2eb0d7859a8e561846f98910326d06c0d51381fed59357bd", + "sha256:26fd7cac7dd51011a245f29a2cc6489c4608b5a8ce8d75661bb4a1066c52dfbe", + "sha256:27b1d3b3915a99208fee9ab092b8184c420f2905b7d7feb4aeb5e4a9c509b8a1", + "sha256:27e98004595899949bd7a7b34e91fa7c44d7a97c40fcaf1d874168bb652ec67e", + "sha256:2b8f60e1b739a74bab7e01fcbe3dddd4657ec685caa04681df9d562ef15b625f", + "sha256:2de29005e11637e7a2361fa151f780ff8eb2543a0da1413bb951e9f14b699ef3", + "sha256:2e8b55d8517a2fda8d95cb45d62a5a8bbf9dd0ad39c5b25c8833efea07b880ca", + "sha256:2fa4331c200c2521512595253f5bb70858b90f750d39b8cbfd67465f8d1b596d", + "sha256:3445e07bf2e8ecfeef6ef67ac83de670358abf2996916039b16a218e3d95e97e", + "sha256:3453e8d41fe5f17d1f8e9c383a7473cd46a63661628ec58e07777c2fff7196dc", + "sha256:378753b4a4de2a7b34063d6f95ae81bfa7b15f2c1a04a9518e8644e81807ebea", + "sha256:3af6e48651c4e0d2d166dc1b033b7042ea3f871504b6805ba5f4fe31581d8d38", + "sha256:3dfcbc95bd7992b16f3f7ba05af8a64ca694331bd24f9157b49dadeeb287493b", + "sha256:3f21f0495edea7fdbaaa87e633a8689cd285f8f4af5c869f27bc8074638ad69c", + "sha256:4041711832360a9b75cfb11b25a6a97c8fb49c07b8bd43d0d02b45d0b499a4ff", + "sha256:44d61b4b7d0c2c9ac019c314e52d7cbda0ae31078aabd0f22e583af3e0d79723", + "sha256:4617e1915a539a0d9a9567795023de41a87106522ff83fbfaf1f6baf8e85437e", + "sha256:4b232061ca880db21fa14defe219840ad9b74b6158adb52ddf0e87bead9e8493", + "sha256:5246b14ca64a8675e0a7161f7af68fe3e910e6b90542b4bfb5439ba752191df6", + "sha256:5725dd9cc02068996d4438d397e255dcb1df776b7ceea3b9cb972bdb11260a83", + "sha256:583f6a1993ca3369e0f80ba99d796d8e6b1a3a2a442dd4e1a79e652116413091", + "sha256:59259dc58e57b10e7e18ce02c311804c10c5a793e6568f8af4dead03264584d1", + "sha256:593eba61ba0c3baae5bc9be2f5232430453fb4432048de28399ca7376de9c627", + "sha256:59f4a79c19232a5774aee369a0c296712ad0e77f24e62cad53160312b1c1eaa1", + "sha256:5f0e260eaf54380380ac3808aa4ebe2d8ca28b9087cf411649f96bad6900c728", + "sha256:62d9cfcf4948683a18a9aff0ab7e1474d407b7bab2ca03116109f8464698ab16", + "sha256:64607d4cbf1b7e3c3c8a14948b99345eda0e161b852e122c6bb71aab6d1d798c", + "sha256:655ca44a831ecb238d124e0402d98f6212ac527a0ba6c55ca26f616604e60a45", + "sha256:666ecce376999bf619756a24ce15bb14c5bfaf04bf00abc7e663ce17c3f34fe7", + "sha256:68049202f67380ff9aa52f12e92b1c30115f32e6895cd7198fa2a7961621fc5a", + "sha256:69803198097467ee7282750acb507fba35ca22cc3b85f16cf45fb01cb9097730", + "sha256:6c7b99ca52c2c1752b544e310101b98a659b720b21db00e65edca34483259967", + "sha256:6dd9412824c4ce1aca56c47b0991e65bebb7ac3f4edccfd3f156150c96a7bf25", + "sha256:70eb60b3ae9245ddea20f8a4190bd79c705a22f8028aaf8bbdebe4716c3fab24", + "sha256:70fb28128acbfd264eda9bf47015537ba3fe86e40d046eb2963d75024be4d055", + "sha256:7b2513ba235829860b13faa931f3b6846548021846ac808455301c23a101689d", + "sha256:7ef9d9da710be50ff6809fed8f1963fecdfecc8b86656cadfca3bc24289414b0", + "sha256:81e69b0a0e2537f26d73b4e43ad7bc8c8efb39621639b4434b76a3de50c6966e", + "sha256:8633e471c6207a039eff6aa116e35f69f3156b3989ea3e2d755f7bc41754a4a7", + "sha256:8bd7c8cfc0b8247c8799080fbff54e0b9619e17cdfeb0478ba7295d43f635d7c", + "sha256:9253fc214112405f0afa7db88739294295f0e08466987f1d70e29930262b4c8f", + "sha256:99b37292234e61325e7a5bb9689e55e48c3f5f603af88b1642666277a81f1fbd", + "sha256:9bd7228827ec7bb817089e2eb301d907c0d9827a9e558f22f762bb690b131652", + "sha256:9beeb01d8c190d7581a4d59522cd3d4b6887040dcfc744af99aa59fef3e041a8", + "sha256:a63cbdd98acef6570c62b92a1e43266f9e8b21e699c363c0fef13bd530799c11", + "sha256:a76e42402542b1fae59798fab64432b2d015ab9d0c8c47ba7addddbaf7952333", + "sha256:ac0a03221cdb5058ce0167ecc92a8c89e8d0decdc9e99a2ec23380793c4dcb96", + "sha256:b0b4136a252cadfa1adb705bb81524eee47d9f6aab4f2ee4fa1e9d3cd4581f64", + "sha256:b25bc607423935079e05619d7de556c91fb6adeae9d5f80868dde3468657994b", + "sha256:b3d504047aba448d70cf6fa22e06cb09f7cbd761939fdd47604f5e007675c24e", + "sha256:bb47271f60660803ad11f4c61b42242b8c1312a31c98c578f79ef9387bbde21c", + "sha256:bbb232860e3d03d544bc03ac57855cd82ddf19c7a07651a7c0fdb95e9efea8b9", + "sha256:bc27863442d388870c1809a87507727b799c8460573cfbb6dc0eeaef5a11b5ec", + "sha256:bc51abd01f08117283c5ebf64844a35144a0843ff7b2983e0648e4d3d9f10dbb", + "sha256:be2eb3f2495ba669d2a985f9b426c1797b7d48d6963899276d22f23e33d47e37", + "sha256:bf9db5488121b596dbfc6718c76092fda77b703c1f7533a226a5a9f65248f8ad", + "sha256:c58e2339def52ef6b71b8f36d13c3688ea23fa093353f3a4fee2556e62086ec9", + "sha256:cfbc454a2880389dbb9b5b398e50d439e2e58669160f27b60e5eca11f68ae17c", + "sha256:cff63a0272fcd259dcc3be1657b07c929c466b067ceb1c20060e8d10af56f5bf", + "sha256:d115bffdd417c6d806ea9069237a4ae02f513b778e3789a359bc5856e0404cc4", + "sha256:d20cfb4e099748ea39e6f7b16c91ab057989712d31761d3300d43134e26e165f", + "sha256:d48424e39c2611ee1b84ad0f44fb3b2b53d473e65de061e3f460fc0be5f1939d", + "sha256:e0fa2d4ec53dc51cf7d3bb22e0aa0143966119f42a0c3e4998293a3dd2856b09", + "sha256:e32fee8ab45d3c2db6da19a5323bc3362237c8b653c70194414b892fd06a080d", + "sha256:e35ba67d65d49080e8e5a1dd40101fccdd9798adb9b050ff670b7d74fa41c566", + "sha256:e3fb866d9932a3d7d0c82da76d816996d1667c44891bd861a0f97ba27e84fc74", + "sha256:e61b02c3f7a1e0b75e20c3978f7135fd13cb6cf551bf4a6d29b999a88830a338", + "sha256:e67ba3c290821343c192f7eae1d8fd5999ca2dc99994114643e2f2d3e6138b15", + "sha256:e79dd39f1e8c3504be0607e5fc6e86bb60fe3584bec8b782578c3b0fde8d932c", + "sha256:e89391e6d60251560f0a8f4bd32137b077a80d9b7dbe6d5cab1cd80d2746f648", + "sha256:ea7433ce7e4bfc3a85654aeb6747babe3f66eaf9a1d0c1e7a4435bbdf27fea84", + "sha256:eaf16ae9ae519a0e237a0f528fd9f0197b9bb70f40263ee57ae53c2b8d48aeb3", + "sha256:eb0c341fa71df5a4595f9501df4ac5abfb5a09580081dffbd1ddd4654e6e9123", + "sha256:f276b245347e6e36526cbd4a266a417796fc531ddf391e43574cf6466c492520", + "sha256:f47ad3d5f3258bd7058d2d506852217865afefe6153a36eb4b6928758041d831", + "sha256:f56a6b404f74ab372da986d240e2e002769a7d7102cc73eb238a4f72eec5284e", + "sha256:f5cf2a0c2bdadf3791b5c205d55a37a54025c6e18a71c71f82bb536cf9a454bf", + "sha256:f5d36399a1b96e1a5fdc91e0522544580dbebeb1f77f27b2b0ab25559e103b8b", + "sha256:f60bd8423be1d9d833f230fdbccf8f57af322d96bcad6599e5a771b151398eb2", + "sha256:f612463ac081803f243ff13cccc648578e2279295048f2a8d5eb430af2bae6e3", + "sha256:f73d3fef726b3243a811121de45193c0ca75f6407fe66f3f4e183c983573e130", + "sha256:f82a116a1d03628a8ace4859556fb39fd1424c933341a08ea3ed6de1edb0283b", + "sha256:fb0ba113b4983beac1a2eb16faffd76cb41e176bf58c4afe3e14b9c681f702de", + "sha256:fb4f868f712b2dd4bcc538b0a0c1f63a2b1d584c925e69a224d759e7070a12d5", + "sha256:fb6116dfb8d1925cbdb52595560584db42a7f664617a1f7d7f6e32f138cdf37d", + "sha256:fda7cb070f442bf80b642cd56483b5548e43d366fe3f39b98e67cce780cded00", + "sha256:feea821ee2a9273771bae61194004ee2fc33f8ec7db08117ef9147d4bbcbca8e" ], "markers": "python_version >= '3.9'", - "version": "==0.22.1" + "version": "==0.22.3" }, "six": { "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", + "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", - "version": "==1.16.0" + "version": "==1.17.0" }, "sqlalchemy": { "hashes": [ @@ -1893,72 +1891,72 @@ }, "coverage": { "hashes": [ - "sha256:093896e530c38c8e9c996901858ac63f3d4171268db2c9c8b373a228f459bbc5", - "sha256:09b9f848b28081e7b975a3626e9081574a7b9196cde26604540582da60235fdf", - "sha256:0b0c69f4f724c64dfbfe79f5dfb503b42fe6127b8d479b2677f2b227478db2eb", - "sha256:13618bed0c38acc418896005732e565b317aa9e98d855a0e9f211a7ffc2d6638", - "sha256:13690e923a3932e4fad4c0ebfb9cb5988e03d9dcb4c5150b5fcbf58fd8bddfc4", - "sha256:177f01eeaa3aee4a5ffb0d1439c5952b53d5010f86e9d2667963e632e30082cc", - "sha256:193e3bffca48ad74b8c764fb4492dd875038a2f9925530cb094db92bb5e47bed", - "sha256:1defe91d41ce1bd44b40fabf071e6a01a5aa14de4a31b986aa9dfd1b3e3e414a", - "sha256:1f188a2402f8359cf0c4b1fe89eea40dc13b52e7b4fd4812450da9fcd210181d", - "sha256:202a2d645c5a46b84992f55b0a3affe4f0ba6b4c611abec32ee88358db4bb649", - "sha256:24eda3a24a38157eee639ca9afe45eefa8d2420d49468819ac5f88b10de84f4c", - "sha256:2e4e0f60cb4bd7396108823548e82fdab72d4d8a65e58e2c19bbbc2f1e2bfa4b", - "sha256:379c111d3558272a2cae3d8e57e6b6e6f4fe652905692d54bad5ea0ca37c5ad4", - "sha256:37cda8712145917105e07aab96388ae76e787270ec04bcb9d5cc786d7cbb8443", - "sha256:38c51297b35b3ed91670e1e4efb702b790002e3245a28c76e627478aa3c10d83", - "sha256:3985b9be361d8fb6b2d1adc9924d01dec575a1d7453a14cccd73225cb79243ee", - "sha256:3988665ee376abce49613701336544041f2117de7b7fbfe91b93d8ff8b151c8e", - "sha256:3ac47fa29d8d41059ea3df65bd3ade92f97ee4910ed638e87075b8e8ce69599e", - "sha256:3b4b4299dd0d2c67caaaf286d58aef5e75b125b95615dda4542561a5a566a1e3", - "sha256:3ea8bb1ab9558374c0ab591783808511d135a833c3ca64a18ec927f20c4030f0", - "sha256:3fe47da3e4fda5f1abb5709c156eca207eacf8007304ce3019eb001e7a7204cb", - "sha256:428ac484592f780e8cd7b6b14eb568f7c85460c92e2a37cb0c0e5186e1a0d076", - "sha256:44e6c85bbdc809383b509d732b06419fb4544dca29ebe18480379633623baafb", - "sha256:4674f0daa1823c295845b6a740d98a840d7a1c11df00d1fd62614545c1583787", - "sha256:4be32da0c3827ac9132bb488d331cb32e8d9638dd41a0557c5569d57cf22c9c1", - "sha256:4db3ed6a907b555e57cc2e6f14dc3a4c2458cdad8919e40b5357ab9b6db6c43e", - "sha256:5c52a036535d12590c32c49209e79cabaad9f9ad8aa4cbd875b68c4d67a9cbce", - "sha256:629a1ba2115dce8bf75a5cce9f2486ae483cb89c0145795603d6554bdc83e801", - "sha256:62a66ff235e4c2e37ed3b6104d8b478d767ff73838d1222132a7a026aa548764", - "sha256:63068a11171e4276f6ece913bde059e77c713b48c3a848814a6537f35afb8365", - "sha256:63c19702db10ad79151a059d2d6336fe0c470f2e18d0d4d1a57f7f9713875dcf", - "sha256:644ec81edec0f4ad17d51c838a7d01e42811054543b76d4ba2c5d6af741ce2a6", - "sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71", - "sha256:6f4548c5ead23ad13fb7a2c8ea541357474ec13c2b736feb02e19a3085fac002", - "sha256:716a78a342679cd1177bc8c2fe957e0ab91405bd43a17094324845200b2fddf4", - "sha256:74610105ebd6f33d7c10f8907afed696e79c59e3043c5f20eaa3a46fddf33b4c", - "sha256:768939f7c4353c0fac2f7c37897e10b1414b571fd85dd9fc49e6a87e37a2e0d8", - "sha256:86cffe9c6dfcfe22e28027069725c7f57f4b868a3f86e81d1c62462764dc46d4", - "sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146", - "sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc", - "sha256:912e95017ff51dc3d7b6e2be158dedc889d9a5cc3382445589ce554f1a34c0ea", - "sha256:9a7b8ac36fd688c8361cbc7bf1cb5866977ece6e0b17c34aa0df58bda4fa18a4", - "sha256:9e89d5c8509fbd6c03d0dd1972925b22f50db0792ce06324ba069f10787429ad", - "sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28", - "sha256:b07c25d52b1c16ce5de088046cd2432b30f9ad5e224ff17c8f496d9cb7d1d451", - "sha256:b39e6011cd06822eb964d038d5dff5da5d98652b81f5ecd439277b32361a3a50", - "sha256:bd55f8fc8fa494958772a2a7302b0354ab16e0b9272b3c3d83cdb5bec5bd1779", - "sha256:c15b32a7aca8038ed7644f854bf17b663bc38e1671b5d6f43f9a2b2bd0c46f63", - "sha256:c1b4474beee02ede1eef86c25ad4600a424fe36cff01a6103cb4533c6bf0169e", - "sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc", - "sha256:c9ebfb2507751f7196995142f057d1324afdab56db1d9743aab7f50289abd022", - "sha256:d7ad66e8e50225ebf4236368cc43c37f59d5e6728f15f6e258c8639fa0dd8e6d", - "sha256:d82ab6816c3277dc962cfcdc85b1efa0e5f50fb2c449432deaf2398a2928ab94", - "sha256:d9fd2547e6decdbf985d579cf3fc78e4c1d662b9b0ff7cc7862baaab71c9cc5b", - "sha256:de38add67a0af869b0d79c525d3e4588ac1ffa92f39116dbe0ed9753f26eba7d", - "sha256:e19122296822deafce89a0c5e8685704c067ae65d45e79718c92df7b3ec3d331", - "sha256:e44961e36cb13c495806d4cac67640ac2866cb99044e210895b506c26ee63d3a", - "sha256:e4c81ed2820b9023a9a90717020315e63b17b18c274a332e3b6437d7ff70abe0", - "sha256:e683e6ecc587643f8cde8f5da6768e9d165cd31edf39ee90ed7034f9ca0eefee", - "sha256:f39e2f3530ed1626c66e7493be7a8423b023ca852aacdc91fb30162c350d2a92", - "sha256:f56f49b2553d7dd85fd86e029515a221e5c1f8cb3d9c38b470bc38bde7b8445a", - "sha256:fb9fc32399dca861584d96eccd6c980b69bbcd7c228d06fb74fe53e007aa8ef9" + "sha256:0824a28ec542a0be22f60c6ac36d679e0e262e5353203bea81d44ee81fe9c6d4", + "sha256:085161be5f3b30fd9b3e7b9a8c301f935c8313dcf928a07b116324abea2c1c2c", + "sha256:0ae1387db4aecb1f485fb70a6c0148c6cdaebb6038f1d40089b1fc84a5db556f", + "sha256:0d59fd927b1f04de57a2ba0137166d31c1a6dd9e764ad4af552912d70428c92b", + "sha256:0f957943bc718b87144ecaee70762bc2bc3f1a7a53c7b861103546d3a403f0a6", + "sha256:13a9e2d3ee855db3dd6ea1ba5203316a1b1fd8eaeffc37c5b54987e61e4194ae", + "sha256:1a330812d9cc7ac2182586f6d41b4d0fadf9be9049f350e0efb275c8ee8eb692", + "sha256:22be16571504c9ccea919fcedb459d5ab20d41172056206eb2994e2ff06118a4", + "sha256:2d10e07aa2b91835d6abec555ec8b2733347956991901eea6ffac295f83a30e4", + "sha256:35371f8438028fdccfaf3570b31d98e8d9eda8bb1d6ab9473f5a390969e98717", + "sha256:3c026eb44f744acaa2bda7493dad903aa5bf5fc4f2554293a798d5606710055d", + "sha256:41ff7b0da5af71a51b53f501a3bac65fb0ec311ebed1632e58fc6107f03b9198", + "sha256:4401ae5fc52ad8d26d2a5d8a7428b0f0c72431683f8e63e42e70606374c311a1", + "sha256:44349150f6811b44b25574839b39ae35291f6496eb795b7366fef3bd3cf112d3", + "sha256:447af20e25fdbe16f26e84eb714ba21d98868705cb138252d28bc400381f6ffb", + "sha256:4a8d8977b0c6ef5aeadcb644da9e69ae0dcfe66ec7f368c89c72e058bd71164d", + "sha256:4e12ae8cc979cf83d258acb5e1f1cf2f3f83524d1564a49d20b8bec14b637f08", + "sha256:592ac539812e9b46046620341498caf09ca21023c41c893e1eb9dbda00a70cbf", + "sha256:5e6b86b5847a016d0fbd31ffe1001b63355ed309651851295315031ea7eb5a9b", + "sha256:608a7fd78c67bee8936378299a6cb9f5149bb80238c7a566fc3e6717a4e68710", + "sha256:61f70dc68bd36810972e55bbbe83674ea073dd1dcc121040a08cdf3416c5349c", + "sha256:65dad5a248823a4996724a88eb51d4b31587aa7aa428562dbe459c684e5787ae", + "sha256:777abfab476cf83b5177b84d7486497e034eb9eaea0d746ce0c1268c71652077", + "sha256:7e216d8044a356fc0337c7a2a0536d6de07888d7bcda76febcb8adc50bdbbd00", + "sha256:85d9636f72e8991a1706b2b55b06c27545448baf9f6dbf51c4004609aacd7dcb", + "sha256:899b8cd4781c400454f2f64f7776a5d87bbd7b3e7f7bda0cb18f857bb1334664", + "sha256:8a289d23d4c46f1a82d5db4abeb40b9b5be91731ee19a379d15790e53031c014", + "sha256:8d2dfa71665a29b153a9681edb1c8d9c1ea50dfc2375fb4dac99ea7e21a0bcd9", + "sha256:8e3c3e38930cfb729cb8137d7f055e5a473ddaf1217966aa6238c88bd9fd50e6", + "sha256:8f8770dfc6e2c6a2d4569f411015c8d751c980d17a14b0530da2d7f27ffdd88e", + "sha256:932fc826442132dde42ee52cf66d941f581c685a6313feebed358411238f60f9", + "sha256:96d636c77af18b5cb664ddf12dab9b15a0cfe9c0bde715da38698c8cea748bfa", + "sha256:97ddc94d46088304772d21b060041c97fc16bdda13c6c7f9d8fcd8d5ae0d8611", + "sha256:98caba4476a6c8d59ec1eb00c7dd862ba9beca34085642d46ed503cc2d440d4b", + "sha256:9901d36492009a0a9b94b20e52ebfc8453bf49bb2b27bca2c9706f8b4f5a554a", + "sha256:99e266ae0b5d15f1ca8d278a668df6f51cc4b854513daab5cae695ed7b721cf8", + "sha256:9c38bf15a40ccf5619fa2fe8f26106c7e8e080d7760aeccb3722664c8656b030", + "sha256:a27801adef24cc30871da98a105f77995e13a25a505a0161911f6aafbd66e678", + "sha256:abd3e72dd5b97e3af4246cdada7738ef0e608168de952b837b8dd7e90341f015", + "sha256:adb697c0bd35100dc690de83154627fbab1f4f3c0386df266dded865fc50a902", + "sha256:b12c6b18269ca471eedd41c1b6a1065b2f7827508edb9a7ed5555e9a56dcfc97", + "sha256:b9389a429e0e5142e69d5bf4a435dd688c14478a19bb901735cdf75e57b13845", + "sha256:ba9e7484d286cd5a43744e5f47b0b3fb457865baf07bafc6bee91896364e1419", + "sha256:bb5555cff66c4d3d6213a296b360f9e1a8e323e74e0426b6c10ed7f4d021e464", + "sha256:be57b6d56e49c2739cdf776839a92330e933dd5e5d929966fbbd380c77f060be", + "sha256:c69e42c892c018cd3c8d90da61d845f50a8243062b19d228189b0224150018a9", + "sha256:ccc660a77e1c2bf24ddbce969af9447a9474790160cfb23de6be4fa88e3951c7", + "sha256:d5275455b3e4627c8e7154feaf7ee0743c2e7af82f6e3b561967b1cca755a0be", + "sha256:d75cded8a3cff93da9edc31446872d2997e327921d8eed86641efafd350e1df1", + "sha256:d872ec5aeb086cbea771c573600d47944eea2dcba8be5f3ee649bfe3cb8dc9ba", + "sha256:d891c136b5b310d0e702e186d70cd16d1119ea8927347045124cb286b29297e5", + "sha256:db1dab894cc139f67822a92910466531de5ea6034ddfd2b11c0d4c6257168073", + "sha256:e28bf44afa2b187cc9f41749138a64435bf340adfcacb5b2290c070ce99839d4", + "sha256:e5ea1cf0872ee455c03e5674b5bca5e3e68e159379c1af0903e89f5eba9ccc3a", + "sha256:e77363e8425325384f9d49272c54045bbed2f478e9dd698dbc65dbc37860eb0a", + "sha256:ee5defd1733fd6ec08b168bd4f5387d5b322f45ca9e0e6c817ea6c4cd36313e3", + "sha256:f1592791f8204ae9166de22ba7e6705fa4ebd02936c09436a1bb85aabca3e599", + "sha256:f2d1ec60d6d256bdf298cb86b78dd715980828f50c46701abc3b0a2b3f8a0dc0", + "sha256:f3ca78518bc6bc92828cd11867b121891d75cae4ea9e908d72030609b996db1b", + "sha256:f7b15f589593110ae767ce997775d645b47e5cbbf54fd322f8ebea6277466cec", + "sha256:fd1213c86e48dfdc5a0cc676551db467495a95a662d2396ecd58e719191446e1", + "sha256:ff74026a461eb0660366fb01c650c1d00f833a086b336bdad7ab00cc952072b3" ], "index": "pypi", "markers": "python_version >= '3.9'", - "version": "==7.6.8" + "version": "==7.6.9" }, "idna": { "hashes": [ diff --git a/dbrepo-search-service/init/Pipfile.lock b/dbrepo-search-service/init/Pipfile.lock index c08493b2f9..9f92191fba 100644 --- a/dbrepo-search-service/init/Pipfile.lock +++ b/dbrepo-search-service/init/Pipfile.lock @@ -26,85 +26,85 @@ }, "aiohttp": { "hashes": [ - "sha256:0411777249f25d11bd2964a230b3ffafcbed6cd65d0f2b132bc2b8f5b8c347c7", - "sha256:0a97d657f6cf8782a830bb476c13f7d777cfcab8428ac49dde15c22babceb361", - "sha256:0b5a5009b0159a8f707879dc102b139466d8ec6db05103ec1520394fdd8ea02c", - "sha256:0bcb7f6976dc0b6b56efde13294862adf68dd48854111b422a336fa729a82ea6", - "sha256:14624d96f0d69cf451deed3173079a68c322279be6030208b045ab77e1e8d550", - "sha256:15c4e489942d987d5dac0ba39e5772dcbed4cc9ae3710d1025d5ba95e4a5349c", - "sha256:176f8bb8931da0613bb0ed16326d01330066bb1e172dd97e1e02b1c27383277b", - "sha256:17af09d963fa1acd7e4c280e9354aeafd9e3d47eaa4a6bfbd2171ad7da49f0c5", - "sha256:1a8b13b9950d8b2f8f58b6e5842c4b842b5887e2c32e3f4644d6642f1659a530", - "sha256:202f40fb686e5f93908eee0c75d1e6fbe50a43e9bd4909bf3bf4a56b560ca180", - "sha256:21cbe97839b009826a61b143d3ca4964c8590d7aed33d6118125e5b71691ca46", - "sha256:27935716f8d62c1c73010428db310fd10136002cfc6d52b0ba7bdfa752d26066", - "sha256:282e0a7ddd36ebc411f156aeaa0491e8fe7f030e2a95da532cf0c84b0b70bc66", - "sha256:28f29bce89c3b401a53d6fd4bee401ee943083bf2bdc12ef297c1d63155070b0", - "sha256:2ac9fd83096df36728da8e2f4488ac3b5602238f602706606f3702f07a13a409", - "sha256:30f9f89ae625d412043f12ca3771b2ccec227cc93b93bb1f994db6e1af40a7d3", - "sha256:317251b9c9a2f1a9ff9cd093775b34c6861d1d7df9439ce3d32a88c275c995cd", - "sha256:31de2f10f63f96cc19e04bd2df9549559beadd0b2ee2da24a17e7ed877ca8c60", - "sha256:36df00e0541f264ce42d62280281541a47474dfda500bc5b7f24f70a7f87be7a", - "sha256:39625703540feb50b6b7f938b3856d1f4886d2e585d88274e62b1bd273fae09b", - "sha256:3f5461c77649358610fb9694e790956b4238ac5d9e697a17f63619c096469afe", - "sha256:4313f3bc901255b22f01663eeeae167468264fdae0d32c25fc631d5d6e15b502", - "sha256:442356e8924fe1a121f8c87866b0ecdc785757fd28924b17c20493961b3d6697", - "sha256:44cb1a1326a0264480a789e6100dc3e07122eb8cd1ad6b784a3d47d13ed1d89c", - "sha256:44d323aa80a867cb6db6bebb4bbec677c6478e38128847f2c6b0f70eae984d72", - "sha256:499368eb904566fbdf1a3836a1532000ef1308f34a1bcbf36e6351904cced771", - "sha256:4b01d9cfcb616eeb6d40f02e66bebfe7b06d9f2ef81641fdd50b8dd981166e0b", - "sha256:5720ebbc7a1b46c33a42d489d25d36c64c419f52159485e55589fbec648ea49a", - "sha256:5cc5e0d069c56645446c45a4b5010d4b33ac6c5ebfd369a791b5f097e46a3c08", - "sha256:618b18c3a2360ac940a5503da14fa4f880c5b9bc315ec20a830357bcc62e6bae", - "sha256:6435a66957cdba1a0b16f368bde03ce9c79c57306b39510da6ae5312a1a5b2c1", - "sha256:647ec5bee7e4ec9f1034ab48173b5fa970d9a991e565549b965e93331f1328fe", - "sha256:6e1e9e447856e9b7b3d38e1316ae9a8c92e7536ef48373de758ea055edfd5db5", - "sha256:6ef1550bb5f55f71b97a6a395286db07f7f2c01c8890e613556df9a51da91e8d", - "sha256:6ffa45cc55b18d4ac1396d1ddb029f139b1d3480f1594130e62bceadf2e1a838", - "sha256:77f31cebd8c27a36af6c7346055ac564946e562080ee1a838da724585c67474f", - "sha256:7a3b5b2c012d70c63d9d13c57ed1603709a4d9d7d473e4a9dfece0e4ea3d5f51", - "sha256:7a7ddf981a0b953ade1c2379052d47ccda2f58ab678fca0671c7c7ca2f67aac2", - "sha256:84de955314aa5e8d469b00b14d6d714b008087a0222b0f743e7ffac34ef56aff", - "sha256:8dcfd14c712aa9dd18049280bfb2f95700ff6a8bde645e09f17c3ed3f05a0130", - "sha256:928f92f80e2e8d6567b87d3316c1fd9860ccfe36e87a9a7f5237d4cda8baa1ba", - "sha256:9384b07cfd3045b37b05ed002d1c255db02fb96506ad65f0f9b776b762a7572e", - "sha256:96726839a42429318017e67a42cca75d4f0d5248a809b3cc2e125445edd7d50d", - "sha256:96bbec47beb131bbf4bae05d8ef99ad9e5738f12717cfbbf16648b78b0232e87", - "sha256:9bcf97b971289be69638d8b1b616f7e557e1342debc7fc86cf89d3f08960e411", - "sha256:a0cf4d814689e58f57ecd5d8c523e6538417ca2e72ff52c007c64065cef50fb2", - "sha256:a7c6147c6306f537cff59409609508a1d2eff81199f0302dd456bb9e7ea50c39", - "sha256:a9266644064779840feec0e34f10a89b3ff1d2d6b751fe90017abcad1864fa7c", - "sha256:afbe85b50ade42ddff5669947afde9e8a610e64d2c80be046d67ec4368e555fa", - "sha256:afcda759a69c6a8be3aae764ec6733155aa4a5ad9aad4f398b52ba4037942fe3", - "sha256:b2fab23003c4bb2249729a7290a76c1dda38c438300fdf97d4e42bf78b19c810", - "sha256:bd3f711f4c99da0091ced41dccdc1bcf8be0281dc314d6d9c6b6cf5df66f37a9", - "sha256:be0c7c98e38a1e3ad7a6ff64af8b6d6db34bf5a41b1478e24c3c74d9e7f8ed42", - "sha256:c1f2d7fd583fc79c240094b3e7237d88493814d4b300d013a42726c35a734bc9", - "sha256:c5bba6b83fde4ca233cfda04cbd4685ab88696b0c8eaf76f7148969eab5e248a", - "sha256:c6beeac698671baa558e82fa160be9761cf0eb25861943f4689ecf9000f8ebd0", - "sha256:c7333e7239415076d1418dbfb7fa4df48f3a5b00f8fdf854fca549080455bc14", - "sha256:c8a02f74ae419e3955af60f570d83187423e42e672a6433c5e292f1d23619269", - "sha256:c9c23e62f3545c2216100603614f9e019e41b9403c47dd85b8e7e5015bf1bde0", - "sha256:cca505829cdab58c2495ff418c96092d225a1bbd486f79017f6de915580d3c44", - "sha256:d3108f0ad5c6b6d78eec5273219a5bbd884b4aacec17883ceefaac988850ce6e", - "sha256:d4b8a1b6c7a68c73191f2ebd3bf66f7ce02f9c374e309bdb68ba886bbbf1b938", - "sha256:d6e274661c74195708fc4380a4ef64298926c5a50bb10fbae3d01627d7a075b7", - "sha256:db2914de2559809fdbcf3e48f41b17a493b58cb7988d3e211f6b63126c55fe82", - "sha256:e738aabff3586091221044b7a584865ddc4d6120346d12e28e788307cd731043", - "sha256:e7f6173302f8a329ca5d1ee592af9e628d3ade87816e9958dcf7cdae2841def7", - "sha256:e9d036a9a41fc78e8a3f10a86c2fc1098fca8fab8715ba9eb999ce4788d35df0", - "sha256:ea142255d4901b03f89cb6a94411ecec117786a76fc9ab043af8f51dd50b5313", - "sha256:ebd3e6b0c7d4954cca59d241970011f8d3327633d555051c430bd09ff49dc494", - "sha256:ec656680fc53a13f849c71afd0c84a55c536206d524cbc831cde80abbe80489e", - "sha256:ec8df0ff5a911c6d21957a9182402aad7bf060eaeffd77c9ea1c16aecab5adbf", - "sha256:ed95d66745f53e129e935ad726167d3a6cb18c5d33df3165974d54742c373868", - "sha256:ef2c9499b7bd1e24e473dc1a85de55d72fd084eea3d8bdeec7ee0720decb54fa", - "sha256:f5252ba8b43906f206048fa569debf2cd0da0316e8d5b4d25abe53307f573941", - "sha256:f737fef6e117856400afee4f17774cdea392b28ecf058833f5eca368a18cf1bf", - "sha256:fc726c3fa8f606d07bd2b500e5dc4c0fd664c59be7788a16b9e34352c50b6b6b" + "sha256:012f176945af138abc10c4a48743327a92b4ca9adc7a0e078077cdb5dbab7be0", + "sha256:02c13415b5732fb6ee7ff64583a5e6ed1c57aa68f17d2bda79c04888dfdc2769", + "sha256:03b6002e20938fc6ee0918c81d9e776bebccc84690e2b03ed132331cca065ee5", + "sha256:04814571cb72d65a6899db6099e377ed00710bf2e3eafd2985166f2918beaf59", + "sha256:0580f2e12de2138f34debcd5d88894786453a76e98febaf3e8fe5db62d01c9bf", + "sha256:06a8e2ee1cbac16fe61e51e0b0c269400e781b13bcfc33f5425912391a542985", + "sha256:076bc454a7e6fd646bc82ea7f98296be0b1219b5e3ef8a488afbdd8e81fbac50", + "sha256:0c9527819b29cd2b9f52033e7fb9ff08073df49b4799c89cb5754624ecd98299", + "sha256:0dc49f42422163efb7e6f1df2636fe3db72713f6cd94688e339dbe33fe06d61d", + "sha256:14cdb5a9570be5a04eec2ace174a48ae85833c2aadc86de68f55541f66ce42ab", + "sha256:15fccaf62a4889527539ecb86834084ecf6e9ea70588efde86e8bc775e0e7542", + "sha256:24213ba85a419103e641e55c27dc7ff03536c4873470c2478cce3311ba1eee7b", + "sha256:31d5093d3acd02b31c649d3a69bb072d539d4c7659b87caa4f6d2bcf57c2fa2b", + "sha256:3691ed7726fef54e928fe26344d930c0c8575bc968c3e239c2e1a04bd8cf7838", + "sha256:386fbe79863eb564e9f3615b959e28b222259da0c48fd1be5929ac838bc65683", + "sha256:3bbbfff4c679c64e6e23cb213f57cc2c9165c9a65d63717108a644eb5a7398df", + "sha256:3de34936eb1a647aa919655ff8d38b618e9f6b7f250cc19a57a4bf7fd2062b6d", + "sha256:40d1c7a7f750b5648642586ba7206999650208dbe5afbcc5284bcec6579c9b91", + "sha256:44224d815853962f48fe124748227773acd9686eba6dc102578defd6fc99e8d9", + "sha256:47ad15a65fb41c570cd0ad9a9ff8012489e68176e7207ec7b82a0940dddfd8be", + "sha256:482cafb7dc886bebeb6c9ba7925e03591a62ab34298ee70d3dd47ba966370d2c", + "sha256:49c7dbbc1a559ae14fc48387a115b7d4bbc84b4a2c3b9299c31696953c2a5219", + "sha256:4b2c7ac59c5698a7a8207ba72d9e9c15b0fc484a560be0788b31312c2c5504e4", + "sha256:4cca22a61b7fe45da8fc73c3443150c3608750bbe27641fc7558ec5117b27fdf", + "sha256:4cfce37f31f20800a6a6620ce2cdd6737b82e42e06e6e9bd1b36f546feb3c44f", + "sha256:502a1464ccbc800b4b1995b302efaf426e8763fadf185e933c2931df7db9a199", + "sha256:53bf2097e05c2accc166c142a2090e4c6fd86581bde3fd9b2d3f9e93dda66ac1", + "sha256:593c114a2221444f30749cc5e5f4012488f56bd14de2af44fe23e1e9894a9c60", + "sha256:5d6958671b296febe7f5f859bea581a21c1d05430d1bbdcf2b393599b1cdce77", + "sha256:5ef359ebc6949e3a34c65ce20230fae70920714367c63afd80ea0c2702902ccf", + "sha256:613e5169f8ae77b1933e42e418a95931fb4867b2991fc311430b15901ed67079", + "sha256:61b9bae80ed1f338c42f57c16918853dc51775fb5cb61da70d590de14d8b5fb4", + "sha256:6362cc6c23c08d18ddbf0e8c4d5159b5df74fea1a5278ff4f2c79aed3f4e9f46", + "sha256:65a96e3e03300b41f261bbfd40dfdbf1c301e87eab7cd61c054b1f2e7c89b9e8", + "sha256:65e55ca7debae8faaffee0ebb4b47a51b4075f01e9b641c31e554fd376595c6c", + "sha256:68386d78743e6570f054fe7949d6cb37ef2b672b4d3405ce91fafa996f7d9b4d", + "sha256:68ff6f48b51bd78ea92b31079817aff539f6c8fc80b6b8d6ca347d7c02384e33", + "sha256:6ab29b8a0beb6f8eaf1e5049252cfe74adbaafd39ba91e10f18caeb0e99ffb34", + "sha256:77ae58586930ee6b2b6f696c82cf8e78c8016ec4795c53e36718365f6959dc82", + "sha256:77c4aa15a89847b9891abf97f3d4048f3c2d667e00f8a623c89ad2dccee6771b", + "sha256:78153314f26d5abef3239b4a9af20c229c6f3ecb97d4c1c01b22c4f87669820c", + "sha256:7852bbcb4d0d2f0c4d583f40c3bc750ee033265d80598d0f9cb6f372baa6b836", + "sha256:7e97d622cb083e86f18317282084bc9fbf261801b0192c34fe4b1febd9f7ae69", + "sha256:7f3dc0e330575f5b134918976a645e79adf333c0a1439dcf6899a80776c9ab39", + "sha256:80886dac673ceaef499de2f393fc80bb4481a129e6cb29e624a12e3296cc088f", + "sha256:811f23b3351ca532af598405db1093f018edf81368e689d1b508c57dcc6b6a32", + "sha256:86a5dfcc39309470bd7b68c591d84056d195428d5d2e0b5ccadfbaf25b026ebc", + "sha256:8b3cf2dc0f0690a33f2d2b2cb15db87a65f1c609f53c37e226f84edb08d10f52", + "sha256:8cc5203b817b748adccb07f36390feb730b1bc5f56683445bfe924fc270b8816", + "sha256:909af95a72cedbefe5596f0bdf3055740f96c1a4baa0dd11fd74ca4de0b4e3f1", + "sha256:974d3a2cce5fcfa32f06b13ccc8f20c6ad9c51802bb7f829eae8a1845c4019ec", + "sha256:98283b94cc0e11c73acaf1c9698dea80c830ca476492c0fe2622bd931f34b487", + "sha256:98f5635f7b74bcd4f6f72fcd85bea2154b323a9f05226a80bc7398d0c90763b0", + "sha256:99b7920e7165be5a9e9a3a7f1b680f06f68ff0d0328ff4079e5163990d046767", + "sha256:9bca390cb247dbfaec3c664326e034ef23882c3f3bfa5fbf0b56cad0320aaca5", + "sha256:9e2e576caec5c6a6b93f41626c9c02fc87cd91538b81a3670b2e04452a63def6", + "sha256:9ef405356ba989fb57f84cac66f7b0260772836191ccefbb987f414bcd2979d9", + "sha256:a55d2ad345684e7c3dd2c20d2f9572e9e1d5446d57200ff630e6ede7612e307f", + "sha256:ab7485222db0959a87fbe8125e233b5a6f01f4400785b36e8a7878170d8c3138", + "sha256:b1fc6b45010a8d0ff9e88f9f2418c6fd408c99c211257334aff41597ebece42e", + "sha256:b78f053a7ecfc35f0451d961dacdc671f4bcbc2f58241a7c820e9d82559844cf", + "sha256:b99acd4730ad1b196bfb03ee0803e4adac371ae8efa7e1cbc820200fc5ded109", + "sha256:be2b516f56ea883a3e14dda17059716593526e10fb6303189aaf5503937db408", + "sha256:beb39a6d60a709ae3fb3516a1581777e7e8b76933bb88c8f4420d875bb0267c6", + "sha256:bf3d1a519a324af764a46da4115bdbd566b3c73fb793ffb97f9111dbc684fc4d", + "sha256:c49a76c1038c2dd116fa443eba26bbb8e6c37e924e2513574856de3b6516be99", + "sha256:c5532f0441fc09c119e1dca18fbc0687e64fbeb45aa4d6a87211ceaee50a74c4", + "sha256:c6b9e6d7e41656d78e37ce754813fa44b455c3d0d0dced2a047def7dc5570b74", + "sha256:c87bf31b7fdab94ae3adbe4a48e711bfc5f89d21cf4c197e75561def39e223bc", + "sha256:cbad88a61fa743c5d283ad501b01c153820734118b65aee2bd7dbb735475ce0d", + "sha256:cf14627232dfa8730453752e9cdc210966490992234d77ff90bc8dc0dce361d5", + "sha256:db1d0b28fcb7f1d35600150c3e4b490775251dea70f894bf15c678fdd84eda6a", + "sha256:ddf5f7d877615f6a1e75971bfa5ac88609af3b74796ff3e06879e8422729fd01", + "sha256:e44a9a3c053b90c6f09b1bb4edd880959f5328cf63052503f892c41ea786d99f", + "sha256:efb15a17a12497685304b2d976cb4939e55137df7b09fa53f1b6a023f01fcb4e", + "sha256:fbbaea811a2bba171197b08eea288b9402faa2bab2ba0858eecdd0a4105753a3" ], "markers": "python_version >= '3.9'", - "version": "==3.11.9" + "version": "==3.11.10" }, "aiosignal": { "hashes": [ @@ -593,64 +593,64 @@ }, "numpy": { "hashes": [ - "sha256:016d0f6f5e77b0f0d45d77387ffa4bb89816b57c835580c3ce8e099ef830befe", - "sha256:02135ade8b8a84011cbb67dc44e07c58f28575cf9ecf8ab304e51c05528c19f0", - "sha256:08788d27a5fd867a663f6fc753fd7c3ad7e92747efc73c53bca2f19f8bc06f48", - "sha256:0d30c543f02e84e92c4b1f415b7c6b5326cbe45ee7882b6b77db7195fb971e3a", - "sha256:0fa14563cc46422e99daef53d725d0c326e99e468a9320a240affffe87852564", - "sha256:13138eadd4f4da03074851a698ffa7e405f41a0845a6b1ad135b81596e4e9958", - "sha256:14e253bd43fc6b37af4921b10f6add6925878a42a0c5fe83daee390bca80bc17", - "sha256:15cb89f39fa6d0bdfb600ea24b250e5f1a3df23f901f51c8debaa6a5d122b2f0", - "sha256:17ee83a1f4fef3c94d16dc1802b998668b5419362c8a4f4e8a491de1b41cc3ee", - "sha256:2312b2aa89e1f43ecea6da6ea9a810d06aae08321609d8dc0d0eda6d946a541b", - "sha256:2564fbdf2b99b3f815f2107c1bbc93e2de8ee655a69c261363a1172a79a257d4", - "sha256:3522b0dfe983a575e6a9ab3a4a4dfe156c3e428468ff08ce582b9bb6bd1d71d4", - "sha256:4394bc0dbd074b7f9b52024832d16e019decebf86caf909d94f6b3f77a8ee3b6", - "sha256:45966d859916ad02b779706bb43b954281db43e185015df6eb3323120188f9e4", - "sha256:4d1167c53b93f1f5d8a139a742b3c6f4d429b54e74e6b57d0eff40045187b15d", - "sha256:4f2015dfe437dfebbfce7c85c7b53d81ba49e71ba7eadbf1df40c915af75979f", - "sha256:50ca6aba6e163363f132b5c101ba078b8cbd3fa92c7865fd7d4d62d9779ac29f", - "sha256:50d18c4358a0a8a53f12a8ba9d772ab2d460321e6a93d6064fc22443d189853f", - "sha256:5641516794ca9e5f8a4d17bb45446998c6554704d888f86df9b200e66bdcce56", - "sha256:576a1c1d25e9e02ed7fa5477f30a127fe56debd53b8d2c89d5578f9857d03ca9", - "sha256:6a4825252fcc430a182ac4dee5a505053d262c807f8a924603d411f6718b88fd", - "sha256:72dcc4a35a8515d83e76b58fdf8113a5c969ccd505c8a946759b24e3182d1f23", - "sha256:747641635d3d44bcb380d950679462fae44f54b131be347d5ec2bce47d3df9ed", - "sha256:762479be47a4863e261a840e8e01608d124ee1361e48b96916f38b119cfda04a", - "sha256:78574ac2d1a4a02421f25da9559850d59457bac82f2b8d7a44fe83a64f770098", - "sha256:825656d0743699c529c5943554d223c021ff0494ff1442152ce887ef4f7561a1", - "sha256:8637dcd2caa676e475503d1f8fdb327bc495554e10838019651b76d17b98e512", - "sha256:96fe52fcdb9345b7cd82ecd34547fca4321f7656d500eca497eb7ea5a926692f", - "sha256:973faafebaae4c0aaa1a1ca1ce02434554d67e628b8d805e61f874b84e136b09", - "sha256:996bb9399059c5b82f76b53ff8bb686069c05acc94656bb259b1d63d04a9506f", - "sha256:a38c19106902bb19351b83802531fea19dee18e5b37b36454f27f11ff956f7fc", - "sha256:a6b46587b14b888e95e4a24d7b13ae91fa22386c199ee7b418f449032b2fa3b8", - "sha256:a9f7f672a3388133335589cfca93ed468509cb7b93ba3105fce780d04a6576a0", - "sha256:aa08e04e08aaf974d4458def539dece0d28146d866a39da5639596f4921fd761", - "sha256:b0df3635b9c8ef48bd3be5f862cf71b0a4716fa0e702155c45067c6b711ddcef", - "sha256:b47fbb433d3260adcd51eb54f92a2ffbc90a4595f8970ee00e064c644ac788f5", - "sha256:baed7e8d7481bfe0874b566850cb0b85243e982388b7b23348c6db2ee2b2ae8e", - "sha256:bc6f24b3d1ecc1eebfbf5d6051faa49af40b03be1aaa781ebdadcbc090b4539b", - "sha256:c006b607a865b07cd981ccb218a04fc86b600411d83d6fc261357f1c0966755d", - "sha256:c181ba05ce8299c7aa3125c27b9c2167bca4a4445b7ce73d5febc411ca692e43", - "sha256:c7662f0e3673fe4e832fe07b65c50342ea27d989f92c80355658c7f888fcc83c", - "sha256:c80e4a09b3d95b4e1cac08643f1152fa71a0a821a2d4277334c88d54b2219a41", - "sha256:c894b4305373b9c5576d7a12b473702afdf48ce5369c074ba304cc5ad8730dff", - "sha256:d7aac50327da5d208db2eec22eb11e491e3fe13d22653dce51b0f4109101b408", - "sha256:d89dd2b6da69c4fff5e39c28a382199ddedc3a5be5390115608345dec660b9e2", - "sha256:d9beb777a78c331580705326d2367488d5bc473b49a9bc3036c154832520aca9", - "sha256:dc258a761a16daa791081d026f0ed4399b582712e6fc887a95af09df10c5ca57", - "sha256:e14e26956e6f1696070788252dcdff11b4aca4c3e8bd166e0df1bb8f315a67cb", - "sha256:e6988e90fcf617da2b5c78902fe8e668361b43b4fe26dbf2d7b0f8034d4cafb9", - "sha256:e711e02f49e176a01d0349d82cb5f05ba4db7d5e7e0defd026328e5cfb3226d3", - "sha256:ea4dedd6e394a9c180b33c2c872b92f7ce0f8e7ad93e9585312b0c5a04777a4a", - "sha256:ecc76a9ba2911d8d37ac01de72834d8849e55473457558e12995f4cd53e778e0", - "sha256:f55ba01150f52b1027829b50d70ef1dafd9821ea82905b63936668403c3b471e", - "sha256:f653490b33e9c3a4c1c01d41bc2aef08f9475af51146e4a7710c450cf9761598", - "sha256:fa2d1337dc61c8dc417fbccf20f6d1e139896a30721b7f1e832b2bb6ef4eb6c4" + "sha256:0557eebc699c1c34cccdd8c3778c9294e8196df27d713706895edc6f57d29608", + "sha256:0798b138c291d792f8ea40fe3768610f3c7dd2574389e37c3f26573757c8f7ef", + "sha256:0da8495970f6b101ddd0c38ace92edea30e7e12b9a926b57f5fabb1ecc25bb90", + "sha256:0f0986e917aca18f7a567b812ef7ca9391288e2acb7a4308aa9d265bd724bdae", + "sha256:122fd2fcfafdefc889c64ad99c228d5a1f9692c3a83f56c292618a59aa60ae83", + "sha256:140dd80ff8981a583a60980be1a655068f8adebf7a45a06a6858c873fcdcd4a0", + "sha256:16757cf28621e43e252c560d25b15f18a2f11da94fea344bf26c599b9cf54b73", + "sha256:18142b497d70a34b01642b9feabb70156311b326fdddd875a9981f34a369b671", + "sha256:1c92113619f7b272838b8d6702a7f8ebe5edea0df48166c47929611d0b4dea69", + "sha256:1e25507d85da11ff5066269d0bd25d06e0a0f2e908415534f3e603d2a78e4ffa", + "sha256:30bf971c12e4365153afb31fc73f441d4da157153f3400b82db32d04de1e4066", + "sha256:3579eaeb5e07f3ded59298ce22b65f877a86ba8e9fe701f5576c99bb17c283da", + "sha256:36b2b43146f646642b425dd2027730f99bac962618ec2052932157e213a040e9", + "sha256:3905a5fffcc23e597ee4d9fb3fcd209bd658c352657548db7316e810ca80458e", + "sha256:3a4199f519e57d517ebd48cb76b36c82da0360781c6a0353e64c0cac30ecaad3", + "sha256:3f2f5cddeaa4424a0a118924b988746db6ffa8565e5829b1841a8a3bd73eb59a", + "sha256:40deb10198bbaa531509aad0cd2f9fadb26c8b94070831e2208e7df543562b74", + "sha256:440cfb3db4c5029775803794f8638fbdbf71ec702caf32735f53b008e1eaece3", + "sha256:4723a50e1523e1de4fccd1b9a6dcea750c2102461e9a02b2ac55ffeae09a4410", + "sha256:4bddbaa30d78c86329b26bd6aaaea06b1e47444da99eddac7bf1e2fab717bd72", + "sha256:4e58666988605e251d42c2818c7d3d8991555381be26399303053b58a5bbf30d", + "sha256:54dc1d6d66f8d37843ed281773c7174f03bf7ad826523f73435deb88ba60d2d4", + "sha256:57fcc997ffc0bef234b8875a54d4058afa92b0b0c4223fc1f62f24b3b5e86038", + "sha256:58b92a5828bd4d9aa0952492b7de803135038de47343b2aa3cc23f3b71a3dc4e", + "sha256:5a145e956b374e72ad1dff82779177d4a3c62bc8248f41b80cb5122e68f22d13", + "sha256:6ab153263a7c5ccaf6dfe7e53447b74f77789f28ecb278c3b5d49db7ece10d6d", + "sha256:7832f9e8eb00be32f15fdfb9a981d6955ea9adc8574c521d48710171b6c55e95", + "sha256:7fe4bb0695fe986a9e4deec3b6857003b4cfe5c5e4aac0b95f6a658c14635e31", + "sha256:7fe8f3583e0607ad4e43a954e35c1748b553bfe9fdac8635c02058023277d1b3", + "sha256:85ad7d11b309bd132d74397fcf2920933c9d1dc865487128f5c03d580f2c3d03", + "sha256:9874bc2ff574c40ab7a5cbb7464bf9b045d617e36754a7bc93f933d52bd9ffc6", + "sha256:a184288538e6ad699cbe6b24859206e38ce5fba28f3bcfa51c90d0502c1582b2", + "sha256:a222d764352c773aa5ebde02dd84dba3279c81c6db2e482d62a3fa54e5ece69b", + "sha256:a50aeff71d0f97b6450d33940c7181b08be1441c6c193e678211bff11aa725e7", + "sha256:a55dc7a7f0b6198b07ec0cd445fbb98b05234e8b00c5ac4874a63372ba98d4ab", + "sha256:a62eb442011776e4036af5c8b1a00b706c5bc02dc15eb5344b0c750428c94219", + "sha256:a7d41d1612c1a82b64697e894b75db6758d4f21c3ec069d841e60ebe54b5b571", + "sha256:a98f6f20465e7618c83252c02041517bd2f7ea29be5378f09667a8f654a5918d", + "sha256:afe8fb968743d40435c3827632fd36c5fbde633b0423da7692e426529b1759b1", + "sha256:b0b227dcff8cdc3efbce66d4e50891f04d0a387cce282fe1e66199146a6a8fca", + "sha256:b30042fe92dbd79f1ba7f6898fada10bdaad1847c44f2dff9a16147e00a93661", + "sha256:b606b1aaf802e6468c2608c65ff7ece53eae1a6874b3765f69b8ceb20c5fa78e", + "sha256:b6207dc8fb3c8cb5668e885cef9ec7f70189bec4e276f0ff70d5aa078d32c88e", + "sha256:c2aed8fcf8abc3020d6a9ccb31dbc9e7d7819c56a348cc88fd44be269b37427e", + "sha256:cb24cca1968b21355cc6f3da1a20cd1cebd8a023e3c5b09b432444617949085a", + "sha256:cff210198bb4cae3f3c100444c5eaa573a823f05c253e7188e1362a5555235b3", + "sha256:d35717333b39d1b6bb8433fa758a55f1081543de527171543a2b710551d40881", + "sha256:df12a1f99b99f569a7c2ae59aa2d31724e8d835fc7f33e14f4792e3071d11221", + "sha256:e09d40edfdb4e260cb1567d8ae770ccf3b8b7e9f0d9b5c2a9992696b30ce2742", + "sha256:e12c6c1ce84628c52d6367863773f7c8c8241be554e8b79686e91a43f1733773", + "sha256:e2b8cd48a9942ed3f85b95ca4105c45758438c7ed28fff1e4ce3e57c3b589d8e", + "sha256:e500aba968a48e9019e42c0c199b7ec0696a97fa69037bea163b55398e390529", + "sha256:ebe5e59545401fbb1b24da76f006ab19734ae71e703cdb4a8b347e84a0cece67", + "sha256:f0dd071b95bbca244f4cb7f70b77d2ff3aaaba7fa16dc41f58d14854a6204e6c", + "sha256:f8c8b141ef9699ae777c6278b52c706b653bf15d135d302754f6b2e90eb30367" ], "markers": "python_version == '3.11'", - "version": "==2.1.3" + "version": "==2.2.0" }, "opensearch-py": { "hashes": [ @@ -995,11 +995,11 @@ }, "six": { "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", + "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", - "version": "==1.16.0" + "version": "==1.17.0" }, "testcontainers-core": { "hashes": [ @@ -1227,72 +1227,72 @@ "develop": { "coverage": { "hashes": [ - "sha256:093896e530c38c8e9c996901858ac63f3d4171268db2c9c8b373a228f459bbc5", - "sha256:09b9f848b28081e7b975a3626e9081574a7b9196cde26604540582da60235fdf", - "sha256:0b0c69f4f724c64dfbfe79f5dfb503b42fe6127b8d479b2677f2b227478db2eb", - "sha256:13618bed0c38acc418896005732e565b317aa9e98d855a0e9f211a7ffc2d6638", - "sha256:13690e923a3932e4fad4c0ebfb9cb5988e03d9dcb4c5150b5fcbf58fd8bddfc4", - "sha256:177f01eeaa3aee4a5ffb0d1439c5952b53d5010f86e9d2667963e632e30082cc", - "sha256:193e3bffca48ad74b8c764fb4492dd875038a2f9925530cb094db92bb5e47bed", - "sha256:1defe91d41ce1bd44b40fabf071e6a01a5aa14de4a31b986aa9dfd1b3e3e414a", - "sha256:1f188a2402f8359cf0c4b1fe89eea40dc13b52e7b4fd4812450da9fcd210181d", - "sha256:202a2d645c5a46b84992f55b0a3affe4f0ba6b4c611abec32ee88358db4bb649", - "sha256:24eda3a24a38157eee639ca9afe45eefa8d2420d49468819ac5f88b10de84f4c", - "sha256:2e4e0f60cb4bd7396108823548e82fdab72d4d8a65e58e2c19bbbc2f1e2bfa4b", - "sha256:379c111d3558272a2cae3d8e57e6b6e6f4fe652905692d54bad5ea0ca37c5ad4", - "sha256:37cda8712145917105e07aab96388ae76e787270ec04bcb9d5cc786d7cbb8443", - "sha256:38c51297b35b3ed91670e1e4efb702b790002e3245a28c76e627478aa3c10d83", - "sha256:3985b9be361d8fb6b2d1adc9924d01dec575a1d7453a14cccd73225cb79243ee", - "sha256:3988665ee376abce49613701336544041f2117de7b7fbfe91b93d8ff8b151c8e", - "sha256:3ac47fa29d8d41059ea3df65bd3ade92f97ee4910ed638e87075b8e8ce69599e", - "sha256:3b4b4299dd0d2c67caaaf286d58aef5e75b125b95615dda4542561a5a566a1e3", - "sha256:3ea8bb1ab9558374c0ab591783808511d135a833c3ca64a18ec927f20c4030f0", - "sha256:3fe47da3e4fda5f1abb5709c156eca207eacf8007304ce3019eb001e7a7204cb", - "sha256:428ac484592f780e8cd7b6b14eb568f7c85460c92e2a37cb0c0e5186e1a0d076", - "sha256:44e6c85bbdc809383b509d732b06419fb4544dca29ebe18480379633623baafb", - "sha256:4674f0daa1823c295845b6a740d98a840d7a1c11df00d1fd62614545c1583787", - "sha256:4be32da0c3827ac9132bb488d331cb32e8d9638dd41a0557c5569d57cf22c9c1", - "sha256:4db3ed6a907b555e57cc2e6f14dc3a4c2458cdad8919e40b5357ab9b6db6c43e", - "sha256:5c52a036535d12590c32c49209e79cabaad9f9ad8aa4cbd875b68c4d67a9cbce", - "sha256:629a1ba2115dce8bf75a5cce9f2486ae483cb89c0145795603d6554bdc83e801", - "sha256:62a66ff235e4c2e37ed3b6104d8b478d767ff73838d1222132a7a026aa548764", - "sha256:63068a11171e4276f6ece913bde059e77c713b48c3a848814a6537f35afb8365", - "sha256:63c19702db10ad79151a059d2d6336fe0c470f2e18d0d4d1a57f7f9713875dcf", - "sha256:644ec81edec0f4ad17d51c838a7d01e42811054543b76d4ba2c5d6af741ce2a6", - "sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71", - "sha256:6f4548c5ead23ad13fb7a2c8ea541357474ec13c2b736feb02e19a3085fac002", - "sha256:716a78a342679cd1177bc8c2fe957e0ab91405bd43a17094324845200b2fddf4", - "sha256:74610105ebd6f33d7c10f8907afed696e79c59e3043c5f20eaa3a46fddf33b4c", - "sha256:768939f7c4353c0fac2f7c37897e10b1414b571fd85dd9fc49e6a87e37a2e0d8", - "sha256:86cffe9c6dfcfe22e28027069725c7f57f4b868a3f86e81d1c62462764dc46d4", - "sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146", - "sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc", - "sha256:912e95017ff51dc3d7b6e2be158dedc889d9a5cc3382445589ce554f1a34c0ea", - "sha256:9a7b8ac36fd688c8361cbc7bf1cb5866977ece6e0b17c34aa0df58bda4fa18a4", - "sha256:9e89d5c8509fbd6c03d0dd1972925b22f50db0792ce06324ba069f10787429ad", - "sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28", - "sha256:b07c25d52b1c16ce5de088046cd2432b30f9ad5e224ff17c8f496d9cb7d1d451", - "sha256:b39e6011cd06822eb964d038d5dff5da5d98652b81f5ecd439277b32361a3a50", - "sha256:bd55f8fc8fa494958772a2a7302b0354ab16e0b9272b3c3d83cdb5bec5bd1779", - "sha256:c15b32a7aca8038ed7644f854bf17b663bc38e1671b5d6f43f9a2b2bd0c46f63", - "sha256:c1b4474beee02ede1eef86c25ad4600a424fe36cff01a6103cb4533c6bf0169e", - "sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc", - "sha256:c9ebfb2507751f7196995142f057d1324afdab56db1d9743aab7f50289abd022", - "sha256:d7ad66e8e50225ebf4236368cc43c37f59d5e6728f15f6e258c8639fa0dd8e6d", - "sha256:d82ab6816c3277dc962cfcdc85b1efa0e5f50fb2c449432deaf2398a2928ab94", - "sha256:d9fd2547e6decdbf985d579cf3fc78e4c1d662b9b0ff7cc7862baaab71c9cc5b", - "sha256:de38add67a0af869b0d79c525d3e4588ac1ffa92f39116dbe0ed9753f26eba7d", - "sha256:e19122296822deafce89a0c5e8685704c067ae65d45e79718c92df7b3ec3d331", - "sha256:e44961e36cb13c495806d4cac67640ac2866cb99044e210895b506c26ee63d3a", - "sha256:e4c81ed2820b9023a9a90717020315e63b17b18c274a332e3b6437d7ff70abe0", - "sha256:e683e6ecc587643f8cde8f5da6768e9d165cd31edf39ee90ed7034f9ca0eefee", - "sha256:f39e2f3530ed1626c66e7493be7a8423b023ca852aacdc91fb30162c350d2a92", - "sha256:f56f49b2553d7dd85fd86e029515a221e5c1f8cb3d9c38b470bc38bde7b8445a", - "sha256:fb9fc32399dca861584d96eccd6c980b69bbcd7c228d06fb74fe53e007aa8ef9" + "sha256:0824a28ec542a0be22f60c6ac36d679e0e262e5353203bea81d44ee81fe9c6d4", + "sha256:085161be5f3b30fd9b3e7b9a8c301f935c8313dcf928a07b116324abea2c1c2c", + "sha256:0ae1387db4aecb1f485fb70a6c0148c6cdaebb6038f1d40089b1fc84a5db556f", + "sha256:0d59fd927b1f04de57a2ba0137166d31c1a6dd9e764ad4af552912d70428c92b", + "sha256:0f957943bc718b87144ecaee70762bc2bc3f1a7a53c7b861103546d3a403f0a6", + "sha256:13a9e2d3ee855db3dd6ea1ba5203316a1b1fd8eaeffc37c5b54987e61e4194ae", + "sha256:1a330812d9cc7ac2182586f6d41b4d0fadf9be9049f350e0efb275c8ee8eb692", + "sha256:22be16571504c9ccea919fcedb459d5ab20d41172056206eb2994e2ff06118a4", + "sha256:2d10e07aa2b91835d6abec555ec8b2733347956991901eea6ffac295f83a30e4", + "sha256:35371f8438028fdccfaf3570b31d98e8d9eda8bb1d6ab9473f5a390969e98717", + "sha256:3c026eb44f744acaa2bda7493dad903aa5bf5fc4f2554293a798d5606710055d", + "sha256:41ff7b0da5af71a51b53f501a3bac65fb0ec311ebed1632e58fc6107f03b9198", + "sha256:4401ae5fc52ad8d26d2a5d8a7428b0f0c72431683f8e63e42e70606374c311a1", + "sha256:44349150f6811b44b25574839b39ae35291f6496eb795b7366fef3bd3cf112d3", + "sha256:447af20e25fdbe16f26e84eb714ba21d98868705cb138252d28bc400381f6ffb", + "sha256:4a8d8977b0c6ef5aeadcb644da9e69ae0dcfe66ec7f368c89c72e058bd71164d", + "sha256:4e12ae8cc979cf83d258acb5e1f1cf2f3f83524d1564a49d20b8bec14b637f08", + "sha256:592ac539812e9b46046620341498caf09ca21023c41c893e1eb9dbda00a70cbf", + "sha256:5e6b86b5847a016d0fbd31ffe1001b63355ed309651851295315031ea7eb5a9b", + "sha256:608a7fd78c67bee8936378299a6cb9f5149bb80238c7a566fc3e6717a4e68710", + "sha256:61f70dc68bd36810972e55bbbe83674ea073dd1dcc121040a08cdf3416c5349c", + "sha256:65dad5a248823a4996724a88eb51d4b31587aa7aa428562dbe459c684e5787ae", + "sha256:777abfab476cf83b5177b84d7486497e034eb9eaea0d746ce0c1268c71652077", + "sha256:7e216d8044a356fc0337c7a2a0536d6de07888d7bcda76febcb8adc50bdbbd00", + "sha256:85d9636f72e8991a1706b2b55b06c27545448baf9f6dbf51c4004609aacd7dcb", + "sha256:899b8cd4781c400454f2f64f7776a5d87bbd7b3e7f7bda0cb18f857bb1334664", + "sha256:8a289d23d4c46f1a82d5db4abeb40b9b5be91731ee19a379d15790e53031c014", + "sha256:8d2dfa71665a29b153a9681edb1c8d9c1ea50dfc2375fb4dac99ea7e21a0bcd9", + "sha256:8e3c3e38930cfb729cb8137d7f055e5a473ddaf1217966aa6238c88bd9fd50e6", + "sha256:8f8770dfc6e2c6a2d4569f411015c8d751c980d17a14b0530da2d7f27ffdd88e", + "sha256:932fc826442132dde42ee52cf66d941f581c685a6313feebed358411238f60f9", + "sha256:96d636c77af18b5cb664ddf12dab9b15a0cfe9c0bde715da38698c8cea748bfa", + "sha256:97ddc94d46088304772d21b060041c97fc16bdda13c6c7f9d8fcd8d5ae0d8611", + "sha256:98caba4476a6c8d59ec1eb00c7dd862ba9beca34085642d46ed503cc2d440d4b", + "sha256:9901d36492009a0a9b94b20e52ebfc8453bf49bb2b27bca2c9706f8b4f5a554a", + "sha256:99e266ae0b5d15f1ca8d278a668df6f51cc4b854513daab5cae695ed7b721cf8", + "sha256:9c38bf15a40ccf5619fa2fe8f26106c7e8e080d7760aeccb3722664c8656b030", + "sha256:a27801adef24cc30871da98a105f77995e13a25a505a0161911f6aafbd66e678", + "sha256:abd3e72dd5b97e3af4246cdada7738ef0e608168de952b837b8dd7e90341f015", + "sha256:adb697c0bd35100dc690de83154627fbab1f4f3c0386df266dded865fc50a902", + "sha256:b12c6b18269ca471eedd41c1b6a1065b2f7827508edb9a7ed5555e9a56dcfc97", + "sha256:b9389a429e0e5142e69d5bf4a435dd688c14478a19bb901735cdf75e57b13845", + "sha256:ba9e7484d286cd5a43744e5f47b0b3fb457865baf07bafc6bee91896364e1419", + "sha256:bb5555cff66c4d3d6213a296b360f9e1a8e323e74e0426b6c10ed7f4d021e464", + "sha256:be57b6d56e49c2739cdf776839a92330e933dd5e5d929966fbbd380c77f060be", + "sha256:c69e42c892c018cd3c8d90da61d845f50a8243062b19d228189b0224150018a9", + "sha256:ccc660a77e1c2bf24ddbce969af9447a9474790160cfb23de6be4fa88e3951c7", + "sha256:d5275455b3e4627c8e7154feaf7ee0743c2e7af82f6e3b561967b1cca755a0be", + "sha256:d75cded8a3cff93da9edc31446872d2997e327921d8eed86641efafd350e1df1", + "sha256:d872ec5aeb086cbea771c573600d47944eea2dcba8be5f3ee649bfe3cb8dc9ba", + "sha256:d891c136b5b310d0e702e186d70cd16d1119ea8927347045124cb286b29297e5", + "sha256:db1dab894cc139f67822a92910466531de5ea6034ddfd2b11c0d4c6257168073", + "sha256:e28bf44afa2b187cc9f41749138a64435bf340adfcacb5b2290c070ce99839d4", + "sha256:e5ea1cf0872ee455c03e5674b5bca5e3e68e159379c1af0903e89f5eba9ccc3a", + "sha256:e77363e8425325384f9d49272c54045bbed2f478e9dd698dbc65dbc37860eb0a", + "sha256:ee5defd1733fd6ec08b168bd4f5387d5b322f45ca9e0e6c817ea6c4cd36313e3", + "sha256:f1592791f8204ae9166de22ba7e6705fa4ebd02936c09436a1bb85aabca3e599", + "sha256:f2d1ec60d6d256bdf298cb86b78dd715980828f50c46701abc3b0a2b3f8a0dc0", + "sha256:f3ca78518bc6bc92828cd11867b121891d75cae4ea9e908d72030609b996db1b", + "sha256:f7b15f589593110ae767ce997775d645b47e5cbbf54fd322f8ebea6277466cec", + "sha256:fd1213c86e48dfdc5a0cc676551db467495a95a662d2396ecd58e719191446e1", + "sha256:ff74026a461eb0660366fb01c650c1d00f833a086b336bdad7ab00cc952072b3" ], "index": "pypi", "markers": "python_version >= '3.9'", - "version": "==7.6.8" + "version": "==7.6.9" }, "iniconfig": { "hashes": [ diff --git a/dbrepo-search-service/init/lib/dbrepo-1.5.3.tar.gz b/dbrepo-search-service/init/lib/dbrepo-1.5.3.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..2bb796d8fece2d97c3b2168248ff493dfa24a549 GIT binary patch literal 39620 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%0DwaYtdU30g} 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 literal 0 HcmV?d00001 diff --git a/dbrepo-search-service/lib/dbrepo-1.5.3.tar.gz b/dbrepo-search-service/lib/dbrepo-1.5.3.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..2bb796d8fece2d97c3b2168248ff493dfa24a549 GIT binary patch literal 39620 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%0DwaYtdU30g} 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 literal 0 HcmV?d00001 diff --git a/dbrepo-ui/components/subset/Results.vue b/dbrepo-ui/components/subset/Results.vue index 4ba414309c..ba063dcf5d 100644 --- a/dbrepo-ui/components/subset/Results.vue +++ b/dbrepo-ui/components/subset/Results.vue @@ -171,10 +171,10 @@ export default { } }, mapResults (data) { - this.result.headers = data.headers.map((h) => { + this.result.headers = data.headers.map((header) => { return { - title: Object.keys(h)[0], - value: Object.keys(h)[0], + title: header, + value: header, sortable: false } }) diff --git a/dbrepo-ui/composables/query-service.ts b/dbrepo-ui/composables/query-service.ts index b3c21c6053..119915de27 100644 --- a/dbrepo-ui/composables/query-service.ts +++ b/dbrepo-ui/composables/query-service.ts @@ -84,7 +84,12 @@ export const useQueryService = (): any => { axios.post<QueryResultDto>(`/api/database/${databaseId}/subset`, data, {params: mapFilter(timestamp, page, size), timeout: 600_000}) .then((response) => { console.info('Executed query with id', response.data.id, ' in database with id', databaseId) - resolve(response.data) + const result: QueryResultDto = { + id: 1, + headers: [], + result: response.data + } + resolve(result) }) .catch((error) => { console.error('Failed to execute query', error) @@ -100,7 +105,12 @@ export const useQueryService = (): any => { axios.get<QueryResultDto>(`/api/database/${databaseId}/subset/${queryId}/data`, { params: mapFilter(null, page, size) }) .then((response) => { console.info('Re-executed query in database with id', databaseId) - resolve(response.data) + const result: QueryResultDto = { + id: Number(response.headers['x-id']), + headers: response.headers['x-headers'] ? response.headers['x-headers'].split(',') : [], + result: response.data + } + resolve(result) }) .catch((error) => { console.error('Failed to re-execute query', error) diff --git a/dbrepo-ui/composables/table-service.ts b/dbrepo-ui/composables/table-service.ts index 3d87e68d4f..102d5dfe58 100644 --- a/dbrepo-ui/composables/table-service.ts +++ b/dbrepo-ui/composables/table-service.ts @@ -74,7 +74,12 @@ export const useTableService = (): any => { axios.get<QueryResultDto>(`/api/database/${databaseId}/table/${tableId}/data`, { params: mapFilter(timestamp, page, size) }) .then((response) => { console.info('Got data for table with id', tableId, 'in database with id', databaseId) - resolve(response.data) + const result: QueryResultDto = { + id: tableId, + headers: response.headers['x-headers'] ? response.headers['x-headers'].split(',') : [], + result: response.data + } + resolve(result) }) .catch((error) => { console.error('Failed to get data', error) diff --git a/dbrepo-ui/composables/view-service.ts b/dbrepo-ui/composables/view-service.ts index 5b3a25a149..0c17c353ef 100644 --- a/dbrepo-ui/composables/view-service.ts +++ b/dbrepo-ui/composables/view-service.ts @@ -41,7 +41,12 @@ export const useViewService = (): any => { axios.get<QueryResultDto>(`/api/database/${databaseId}/view/${viewId}/data`, { params: {page, size} }) .then((response) => { console.info('Re-executed view with id', viewId, 'in database with id', databaseId) - resolve(response.data) + const result: QueryResultDto = { + id: viewId, + headers: response.headers['x-headers'] ? response.headers['x-headers'].split(',') : [], + result: response.data + } + resolve(result) }) .catch((error) => { console.error('Failed to re-execute view', error) diff --git a/dbrepo-ui/dto/index.ts b/dbrepo-ui/dto/index.ts index ba4c413042..c068862f67 100644 --- a/dbrepo-ui/dto/index.ts +++ b/dbrepo-ui/dto/index.ts @@ -537,7 +537,7 @@ interface ImportCsv { interface QueryResultDto { id: number | null; result: any; - headers: any; + headers: string[]; } interface TableHistoryDto { diff --git a/dbrepo-ui/locales/en-US.json b/dbrepo-ui/locales/en-US.json index 8b6807bdbf..3317a3ff2a 100644 --- a/dbrepo-ui/locales/en-US.json +++ b/dbrepo-ui/locales/en-US.json @@ -312,8 +312,8 @@ "size": { "title": "Size" }, - "result-rows": { - "title": "Rows" + "rows": { + "title": "Result Rows" }, "owner": { "title": "Owner" diff --git a/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/data.vue b/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/data.vue index d4d707fab8..3ac8f40d64 100644 --- a/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/data.vue +++ b/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/data.vue @@ -148,7 +148,7 @@ export default { const url = URL.createObjectURL(data) const link = document.createElement('a') link.href = url - link.download = 'table.csv' + link.download = 'subset.csv' document.body.appendChild(link) link.click() }) diff --git a/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/info.vue b/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/info.vue index 0d59b1ed25..b9ed74a042 100644 --- a/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/info.vue +++ b/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/info.vue @@ -66,7 +66,7 @@ <pre>{{ $t('pages.subset.hash.prefix') }}:{{ result_hash }}</pre> </v-list-item> <v-list-item - :title="$t('pages.subset.result-rows.title')" + :title="$t('pages.subset.rows.title')" density="compact"> {{ subset.result_number }} </v-list-item> diff --git a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/info.vue b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/info.vue index e109a7db3e..1fb012598a 100644 --- a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/info.vue +++ b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/info.vue @@ -41,7 +41,7 @@ {{ sizeToHumanLabel(table.data_length) }} </v-list-item> <v-list-item - :title="$t('pages.table.result-rows.title')"> + :title="$t('pages.table.rows.title')"> {{ table.num_rows }} </v-list-item> <v-list-item diff --git a/dbrepo-upload-service/pom.xml b/dbrepo-upload-service/pom.xml index 9684d60454..ba4567da8d 100644 --- a/dbrepo-upload-service/pom.xml +++ b/dbrepo-upload-service/pom.xml @@ -5,13 +5,13 @@ <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> - <version>3.1.12</version> + <version>3.3.5</version> </parent> <groupId>at.tuwien</groupId> <artifactId>dbrepo-upload-service</artifactId> <name>dbrepo-upload-service</name> - <version>1.5.2</version> + <version>1.5.3</version> <url>https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/1.5/</url> <developers> @@ -24,11 +24,11 @@ <properties> <java.version>17</java.version> - <spring-cloud.version>4.0.2</spring-cloud.version> + <spring-cloud.version>4.1.4</spring-cloud.version> <mapstruct.version>1.5.5.Final</mapstruct.version> <rabbitmq.version>5.20.0</rabbitmq.version> <jackson-datatype.version>2.15.0</jackson-datatype.version> - <commons-io.version>2.15.0</commons-io.version> + <commons-io.version>2.17.0</commons-io.version> <commons-validator.version>1.8.0</commons-validator.version> <guava.version>33.0.0-jre</guava.version> <jacoco.version>0.8.12</jacoco.version> diff --git a/dbrepo-upload-service/src/main/resources/application.yml b/dbrepo-upload-service/src/main/resources/application.yml index 2d79e7cfae..e0285aa736 100644 --- a/dbrepo-upload-service/src/main/resources/application.yml +++ b/dbrepo-upload-service/src/main/resources/application.yml @@ -2,11 +2,6 @@ application: title: DBRepo version: '@project.version@' spring: - datasource: - url: "jdbc:mariadb://${METADATA_HOST:metadata-db}:${METADATA_PORT:3306}/${METADATA_DB:dbrepo}${METADATA_JDBC_EXTRA_ARGS}" - driver-class-name: org.mariadb.jdbc.Driver - username: "${METADATA_USERNAME:root}" - password: "${METADATA_DB_PASSWORD:dbrepo}" jpa: show-sql: false open-in-view: false @@ -16,13 +11,7 @@ spring: jdbc: time_zone: UTC application: - name: metadata-service - rabbitmq: - host: "${BROKER_HOST:broker-service}" - virtual-host: "${BROKER_VIRTUALHOST:dbrepo}" - username: "${BROKER_USERNAME:admin}" - password: "${BROKER_PASSWORD:admin}" - port: ${BROKER_PORT:5672} + name: upload-service main: banner-mode: off management: @@ -47,38 +36,3 @@ logging: root: warn at.tuwien.: "${LOG_LEVEL:info}" org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver: debug -dbrepo: - repository-name: "${REPOSITORY_NAME:Database Repository}" - base-url: "${BASE_URL:http://localhost}" - admin-email: "${ADMIN_EMAIL:noreply@example.com}" - deleted-record: "${DELETED_RECORD:persistent}" - granularity: "${GRANULARITY:YYYY-MM-DDThh:mm:ssZ}" - exchangeName: "${BROKER_EXCHANGE_NAME:dbrepo}" - queueName: "${BROKER_QUEUE_NAME:dbrepo}" - connectionTimeout: "${SPARQL_CONNECTION_TIMEOUT:10000}" - s3: - accessKeyId: "${S3_ACCESS_KEY_ID:seaweedfsadmin}" - secretAccessKey: "${S3_SECRET_ACCESS_KEY:seaweedfsadmin}" - bucket: "${S3_BUCKET:dbrepo}" - system: - username: "${SYSTEM_USERNAME:admin}" - password: "${SYSTEM_PASSWORD:admin}" - endpoints: - analyseService: "${ANALYSE_SERVICE_ENDPOINT:http://analyse-service:8080}" - searchService: "${SEARCH_SERVICE_ENDPOINT:http://search-service:8080}" - dataService: "${DATA_SERVICE_ENDPOINT:http://data-service:8080}" - brokerService: "${BROKER_SERVICE_ENDPOINT:http://broker-service:15672}" - authService: "${AUTH_SERVICE_ENDPOINT:http://auth-service:8080}" - storageService: "${S3_ENDPOINT:http://storage-service:9000}" - rorService: "${ROR_ENDPOINT:https://api.ror.org}" - crossRefService: "${CROSSREF_ENDPOINT:http://data.crossref.org}" - pid: - base: "${BASE_URL:http://localhost}/pid/" - jwt: - public_key: "${JWT_PUBKEY:MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqqnHQ2BWWW9vDNLRCcxD++xZg/16oqMo/c1l+lcFEjjAIJjJp/HqrPYU/U9GvquGE6PbVFtTzW1KcKawOW+FJNOA3CGo8Q1TFEfz43B8rZpKsFbJKvQGVv1Z4HaKPvLUm7iMm8Hv91cLduuoWx6Q3DPe2vg13GKKEZe7UFghF+0T9u8EKzA/XqQ0OiICmsmYPbwvf9N3bCKsB/Y10EYmZRb8IhCoV9mmO5TxgWgiuNeCTtNCv2ePYqL/U0WvyGFW0reasIK8eg3KrAUj8DpyOgPOVBn3lBGf+3KFSYi+0bwZbJZWqbC/Xlk20Go1YfeJPRIt7ImxD27R/lNjgDO/MwIDAQAB}" - keycloak: - username: "${AUTH_SERVICE_ADMIN:admin}" - password: "${AUTH_SERVICE_ADMIN_PASSWORD:admin}" - client: "${AUTH_SERVICE_CLIENT:dbrepo-client}" - clientSecret: "${AUTH_SERVICE_CLIENT_SECRET:MUwRc7yfXSJwX8AdRMWaQC3Nep1VjwgG}" - website: "${BASE_URL:http://localhost}" diff --git a/dbrepo-upload-service/src/test/resources/application.properties b/dbrepo-upload-service/src/test/resources/application.properties index 088fec498b..cb41084578 100644 --- a/dbrepo-upload-service/src/test/resources/application.properties +++ b/dbrepo-upload-service/src/test/resources/application.properties @@ -4,15 +4,8 @@ spring.profiles.active=local,junit # disable discovery spring.cloud.discovery.enabled=false -# internal datasource -spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_ON_EXIT=FALSE;INIT=CREATE SCHEMA IF NOT EXISTS DBREPO;NON_KEYWORDS=value -spring.datasource.driverClassName=org.h2.Driver -spring.datasource.username=sa -spring.datasource.password=password -spring.jpa.database-platform=org.hibernate.dialect.H2Dialect -spring.sql.init.mode=always -spring.sql.init.schema-locations=classpath*:init/schema.sql -spring.jpa.hibernate.ddl-auto=create +# disable datasource +spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration # logging logging.level.root=error diff --git a/helm/dbrepo/Chart.yaml b/helm/dbrepo/Chart.yaml index abb0c1821a..89ba2eec93 100644 --- a/helm/dbrepo/Chart.yaml +++ b/helm/dbrepo/Chart.yaml @@ -7,8 +7,8 @@ description: Helm Chart for installing DBRepo sources: - https://gitlab.phaidra.org/fair-data-austria-db-repository/fda-services type: application -version: "1.5.2" -appVersion: "1.5.2" +version: "1.5.3" +appVersion: "1.5.3" keywords: - dbrepo maintainers: diff --git a/helm/dbrepo/README.md b/helm/dbrepo/README.md index 759e67bf43..8df445bfd5 100644 --- a/helm/dbrepo/README.md +++ b/helm/dbrepo/README.md @@ -11,7 +11,7 @@ sample [ for your deployment and update the variables, especially `hostname`. ```bash -helm install my-release "oci://registry.datalab.tuwien.ac.at/dbrepo/helm/dbrepo" --values ./values.yaml --version "1.5.2" +helm install my-release "oci://registry.datalab.tuwien.ac.at/dbrepo/helm/dbrepo" --values ./values.yaml --version "1.5.3" ``` ## Prerequisites @@ -28,7 +28,7 @@ helm install my-release "oci://registry.datalab.tuwien.ac.at/dbrepo/helm/dbrepo" To install the chart with the release name `my-release`: ```bash -helm install my-release "oci://oci://registry.datalab.tuwien.ac.at/dbrepo/helm" --values ./values.yaml --version "1.5.2" +helm install my-release "oci://oci://registry.datalab.tuwien.ac.at/dbrepo/helm" --values ./values.yaml --version "1.5.3" ``` The command deploys DBRepo on the Kubernetes cluster in the default configuration. The Parameters section lists the diff --git a/helm/dbrepo/values.yaml b/helm/dbrepo/values.yaml index 1a122d2fe3..afc18212ed 100644 --- a/helm/dbrepo/values.yaml +++ b/helm/dbrepo/values.yaml @@ -369,7 +369,7 @@ analyseservice: enabled: true image: ## @skip analyseservice.image.name - name: registry.datalab.tuwien.ac.at/dbrepo/analyse-service:1.5.2 + name: registry.datalab.tuwien.ac.at/dbrepo/analyse-service:1.5.3 ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod podSecurityContext: ## @param analyseservice.podSecurityContext.enabled Enable pods' Security Context @@ -430,7 +430,7 @@ metadataservice: enabled: true image: ## @skip metadataservice.image.name - name: registry.datalab.tuwien.ac.at/dbrepo/metadata-service:1.5.2 + name: registry.datalab.tuwien.ac.at/dbrepo/metadata-service:1.5.3 ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod podSecurityContext: ## @param metadataservice.podSecurityContext.enabled Enable pods' Security Context @@ -527,7 +527,7 @@ dataservice: endpoint: http://data-service image: ## @skip dataservice.image.name - name: registry.datalab.tuwien.ac.at/dbrepo/data-service:1.5.2 + name: registry.datalab.tuwien.ac.at/dbrepo/data-service:1.5.3 ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod podSecurityContext: ## @param dataservice.podSecurityContext.enabled Enable pods' Security Context @@ -613,7 +613,7 @@ searchservice: endpoint: http://search-service image: ## @skip searchservice.image.name - name: registry.datalab.tuwien.ac.at/dbrepo/search-service:1.5.2 + name: registry.datalab.tuwien.ac.at/dbrepo/search-service:1.5.3 ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod podSecurityContext: ## @param searchservice.podSecurityContext.enabled Enable pods' Security Context @@ -660,7 +660,7 @@ searchservice: init: image: ## @skip searchservice.init.image.name - name: registry.datalab.tuwien.ac.at/dbrepo/search-service-init:1.5.2 + name: registry.datalab.tuwien.ac.at/dbrepo/search-service-init:1.5.3 ## @param searchservice.init.resourcesPreset The container resource preset resourcesPreset: "nano" ## @param searchservice.init.resources Set container requests and limits for different resources like CPU or memory (essential for production workloads) @@ -721,7 +721,7 @@ storageservice: init: image: ## @skip storageservice.init.image.name - name: registry.datalab.tuwien.ac.at/dbrepo/storage-service-init:1.5.2 + name: registry.datalab.tuwien.ac.at/dbrepo/storage-service-init:1.5.3 ## @param storageservice.init.resourcesPreset The container resource preset resourcesPreset: "nano" ## @param storageservice.init.resources Set container requests and limits for different resources like CPU or memory (essential for production workloads) @@ -827,7 +827,7 @@ ui: enabled: true image: ## @skip ui.image.name - name: registry.datalab.tuwien.ac.at/dbrepo/ui:1.5.2 + name: registry.datalab.tuwien.ac.at/dbrepo/ui:1.5.3 ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod podSecurityContext: ## @param ui.podSecurityContext.enabled Enable pods' Security Context diff --git a/install.sh b/install.sh index 12a20e9868..f45f926915 100644 --- a/install.sh +++ b/install.sh @@ -1,7 +1,7 @@ #!/bin/bash # preset -VERSION="1.5.2" +VERSION="1.5.3" MIN_CPU=8 MIN_RAM=4 MIN_MAP_COUNT=262144 diff --git a/lib/python/pyproject.toml b/lib/python/pyproject.toml index e032c5edfe..dae17d3835 100644 --- a/lib/python/pyproject.toml +++ b/lib/python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dbrepo" -version = "1.5.2" +version = "1.5.3" description = "DBRepo Python Library" keywords = [ "DBRepo", diff --git a/lib/python/setup.py b/lib/python/setup.py index 50be6c428a..36bc076393 100644 --- a/lib/python/setup.py +++ b/lib/python/setup.py @@ -2,7 +2,7 @@ from distutils.core import setup setup(name="dbrepo", - version="1.5.2", + version="1.5.3", description="A library for communicating with DBRepo", url="https://www.ifs.tuwien.ac.at/infrastructures/dbrepo/1.5/", author="Martin Weise", diff --git a/sonar-project.properties b/sonar-project.properties index 54228d8d4c..f109f63239 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -2,7 +2,7 @@ sonar.projectKey=fair-data-austria-db-repository_fda-services_a57fa043-ab99-4cdd-a721-162d9a916d77 sonar.host.url=https://s39.datalab.tuwien.ac.at # project -sonar.projectVersion=1.5.2 +sonar.projectVersion=1.5.3 # general sonar.qualitygate.wait=true sonar.projectCreation.mainBranchName=master -- GitLab From 53e094520dcc7e746efd4fec5c6a846b4c4c6455 Mon Sep 17 00:00:00 2001 From: Martin Weise <martin.weise@tuwien.ac.at> Date: Fri, 13 Dec 2024 14:41:52 +0100 Subject: [PATCH 2/5] Hotfix the UI --- .../java/at/tuwien/mapper/DataMapper.java | 16 ++- .../java/at/tuwien/mapper/MariaDbMapper.java | 2 +- .../impl/SubsetServiceMariaDbImpl.java | 2 - .../at/tuwien/endpoints/AccessEndpoint.java | 9 +- dbrepo-ui/components/subset/Results.vue | 47 ++++++- .../[database_id]/subset/[subset_id]/info.vue | 3 +- .../[database_id]/table/[table_id]/data.vue | 125 +++--------------- 7 files changed, 79 insertions(+), 125 deletions(-) diff --git a/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/DataMapper.java b/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/DataMapper.java index ccb49288c5..3e569d2f12 100644 --- a/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/DataMapper.java +++ b/dbrepo-data-service/services/src/main/java/at/tuwien/mapper/DataMapper.java @@ -21,7 +21,6 @@ import at.tuwien.api.database.table.constraints.primary.PrimaryKeyDto; import at.tuwien.api.database.table.constraints.unique.UniqueDto; import at.tuwien.api.user.UserDto; import at.tuwien.config.QueryConfig; -import at.tuwien.exception.QueryNotFoundException; import at.tuwien.exception.TableNotFoundException; import org.apache.hadoop.shaded.com.google.common.hash.Hashing; import org.apache.hadoop.shaded.org.apache.commons.io.FileUtils; @@ -199,17 +198,13 @@ public interface DataMapper { return view; } - default QueryDto resultSetToQueryDto(@NotNull ResultSet data) throws SQLException, QueryNotFoundException { + default QueryDto resultSetToQueryDto(@NotNull ResultSet data) throws SQLException { /* note that next() is called outside this mapping function */ - return QueryDto.builder() + final QueryDto subset = QueryDto.builder() .id(data.getLong(1)) .created(LocalDateTime.parse(data.getString(2), mariaDbFormatter) .atZone(ZoneId.of("UTC")) .toInstant()) - .creator(UserDto.builder() - .id(UUID.fromString(data.getString(3))) - .build()) - .createdBy(UUID.fromString(data.getString(3))) .query(data.getString(4)) .queryHash(data.getString(5)) .resultHash(data.getString(6)) @@ -219,6 +214,13 @@ public interface DataMapper { .atZone(ZoneId.of("UTC")) .toInstant()) .build(); + if (data.getString(3) != null) { + subset.setCreator(UserDto.builder() + .id(UUID.fromString(data.getString(3))) + .build()); + subset.setCreatedBy(UUID.fromString(data.getString(3))); + } + return subset; } default List<TableHistoryDto> resultSetToTableHistory(ResultSet resultSet) throws SQLException { 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 99480719fa..4e91dd7221 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 @@ -119,7 +119,7 @@ public interface MariaDbMapper { } default String queryStoreCreateTableRawQuery() { - final String statement = "CREATE TABLE `qs_queries` ( `id` bigint not null primary key default nextval(`qs_queries_seq`), `created` datetime not null default now(), `executed` datetime not null default now(), `created_by` varchar(36) not null, `query` text not null, `query_normalized` text not null, `is_persisted` boolean not null, `query_hash` varchar(255) not null, `result_hash` varchar(255), `result_number` bigint) WITH SYSTEM VERSIONING;"; + final String statement = "CREATE TABLE `qs_queries` ( `id` bigint not null primary key default nextval(`qs_queries_seq`), `created` datetime not null default now(), `executed` datetime not null default now(), `created_by` varchar(36), `query` text not null, `query_normalized` text not null, `is_persisted` boolean not null, `query_hash` varchar(255) not null, `result_hash` varchar(255), `result_number` bigint) WITH SYSTEM VERSIONING;"; log.trace("mapped create query store table statement: {}", statement); return statement; } 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 1e1e78603b..7e821af01c 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 @@ -180,8 +180,6 @@ public class SubsetServiceMariaDbImpl extends HibernateConnector implements Subs } final QueryDto query = dataMapper.resultSetToQueryDto(resultSet); query.setIdentifiers(metadataServiceGateway.getIdentifiers(database.getId(), queryId)); - query.setCreator(database.getOwner()); - query.setDatabaseId(database.getId()); return query; } catch (SQLException e) { log.error("Failed to find query with id {}: {}", queryId, e.getMessage()); diff --git a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/AccessEndpoint.java b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/AccessEndpoint.java index 3b39857ee6..812ec7bc21 100644 --- a/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/AccessEndpoint.java +++ b/dbrepo-metadata-service/rest-service/src/main/java/at/tuwien/endpoints/AccessEndpoint.java @@ -20,7 +20,6 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import jakarta.validation.Valid; -import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; @@ -92,7 +91,7 @@ public class AccessEndpoint { schema = @Schema(implementation = ApiErrorDto.class))}), }) public ResponseEntity<DatabaseAccessDto> create(@NotNull @PathVariable("databaseId") Long databaseId, - @org.hibernate.validator.constraints.UUID @PathVariable("userId") UUID userId, + @PathVariable("userId") UUID userId, @Valid @RequestBody UpdateDatabaseAccessDto data, @NotNull Principal principal) throws NotAllowedException, DataServiceException, DataServiceConnectionException, DatabaseNotFoundException, UserNotFoundException, AccessNotFoundException, @@ -155,7 +154,7 @@ public class AccessEndpoint { schema = @Schema(implementation = ApiErrorDto.class))}), }) public ResponseEntity<Void> update(@NotNull @PathVariable("databaseId") Long databaseId, - @org.hibernate.validator.constraints.UUID @PathVariable("userId") UUID userId, + @PathVariable("userId") UUID userId, @Valid @RequestBody UpdateDatabaseAccessDto data, @NotNull Principal principal) throws NotAllowedException, DataServiceException, DataServiceConnectionException, DatabaseNotFoundException, UserNotFoundException, @@ -200,7 +199,7 @@ public class AccessEndpoint { schema = @Schema(implementation = ApiErrorDto.class))}), }) public ResponseEntity<DatabaseAccessDto> find(@NotNull @PathVariable("databaseId") Long databaseId, - @org.hibernate.validator.constraints.UUID @PathVariable("userId") UUID userId, + @PathVariable("userId") UUID userId, @NotNull Principal principal) throws DatabaseNotFoundException, UserNotFoundException, AccessNotFoundException, NotAllowedException { log.debug("endpoint get database access, databaseId={}, userId={}, principal.name={}", databaseId, userId, @@ -257,7 +256,7 @@ public class AccessEndpoint { schema = @Schema(implementation = ApiErrorDto.class))}), }) public ResponseEntity<Void> revoke(@NotNull @PathVariable("databaseId") Long databaseId, - @org.hibernate.validator.constraints.UUID @PathVariable("userId") UUID userId, + @PathVariable("userId") UUID userId, @NotNull Principal principal) throws NotAllowedException, DataServiceException, DataServiceConnectionException, DatabaseNotFoundException, UserNotFoundException, AccessNotFoundException, SearchServiceException, SearchServiceConnectionException { diff --git a/dbrepo-ui/components/subset/Results.vue b/dbrepo-ui/components/subset/Results.vue index ba063dcf5d..e9e3c0deb5 100644 --- a/dbrepo-ui/components/subset/Results.vue +++ b/dbrepo-ui/components/subset/Results.vue @@ -18,13 +18,15 @@ export default { props: { type: { type: String, - default: () => 'query' /* query or view */ + default: () => 'query' /* query, view or table */ }, loading: { type: Boolean, - default: () => { - return false - } + default: () => false + }, + timestamp: { + type: String, + default: () => new Date().toISOString() } }, data () { @@ -106,6 +108,25 @@ export default { .finally(() => { this.loadingExecute = false }) + } else if (this.type === 'table') { + const tableService = useTableService() + tableService.getData(this.$route.params.database_id, id, (this.options.page - 1), this.options.itemsPerPage, this.timestamp) + .then((result) => { + this.mapResults(result) + this.id = id + this.loadingExecute = false + }) + .catch(({code}) => { + this.loadingExecute = false + const toast = useToastInstance() + if (typeof code !== 'string') { + return + } + toast.error(this.$t(code)) + }) + .finally(() => { + this.loadingExecute = false + }) } else { const viewService = useViewService() viewService.reExecuteData(this.$route.params.database_id, id, this.options.page - 1, this.options.itemsPerPage) @@ -150,6 +171,24 @@ export default { .finally(() => { this.loadingCount = false }) + } else if (this.type === 'table') { + const tableService = useTableService() + tableService.getCount(this.$route.params.database_id, id, this.timestamp) + .then((count) => { + this.total = count + this.loadingCount = false + }) + .catch(({code}) => { + this.loadingCount = false + const toast = useToastInstance() + if (typeof code !== 'string') { + return + } + toast.error(this.$t(code)) + }) + .finally(() => { + this.loadingCount = false + }) } else { const viewService = useViewService() viewService.reExecuteCount(this.$route.params.database_id, id) diff --git a/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/info.vue b/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/info.vue index b9ed74a042..764d1b55ff 100644 --- a/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/info.vue +++ b/dbrepo-ui/pages/database/[database_id]/subset/[subset_id]/info.vue @@ -30,7 +30,7 @@ width="50%" /> </v-list> <v-list - v-else + v-else-if="subset" lines="two" dense> <v-list-item @@ -40,6 +40,7 @@ {{ database.is_public ? $t('toolbars.database.public') : $t('toolbars.database.private') }} </v-list-item> <v-list-item + v-if="subset.creator" :title="$t('pages.subset.creator.title')" density="compact"> <UserBadge :user="subset.creator" :other-user="user" /> diff --git a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/data.vue b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/data.vue index 1be05e4bf1..1a29f918bf 100644 --- a/dbrepo-ui/pages/database/[database_id]/table/[table_id]/data.vue +++ b/dbrepo-ui/pages/database/[database_id]/table/[table_id]/data.vue @@ -55,36 +55,19 @@ </v-toolbar> <TimeDrift /> <v-card - elevation="0" - tile> - <v-card - v-if="error" - variant="flat"> - <v-card-text> - {{ $t('error.table.connection') }} - </v-card-text> - </v-card> - <v-data-table-server - v-if="!error" - v-model="selection" - flat - :show-select="canModify" - return-object - :headers="headers" - :items="rows" - :items-length="total" - :loading="loadingData || loadingCount" - :options.sync="options" - :footer-props="footerProps" - :items-per-page-options="footerProps.itemsPerPageOptions" - @update:options="loadData"> - <template - v-for="(blobColumn, idx) in blobColumns" - v-slot:[blobColumn]="{ item }"> - <BlobDownload - :blob="item[blobColumn.substring(5)]" /> - </template> - </v-data-table-server> + v-if="error" + variant="flat"> + <v-card-text> + {{ $t('error.table.connection') }} + </v-card-text> + </v-card> + <v-card tile> + <QueryResults + id="query-results" + ref="queryResults" + type="table" + :timestamp="versionISO || lastReload.toISOString()" + class="mt-0 mb-0" /> </v-card> <v-dialog v-model="pickVersionDialog" @@ -122,14 +105,16 @@ import TableHistory from '@/components/table/TableHistory.vue' import TimeDrift from '@/components/TimeDrift.vue' import TableToolbar from '@/components/table/TableToolbar.vue' -import {formatTimestampUTC, formatDateUTC, formatTimestamp} from '@/utils' +import { formatTimestamp } from '@/utils' import { useUserStore } from '@/stores/user' import { useCacheStore } from '@/stores/cache' import EditTuple from '@/components/dialogs/EditTuple.vue' import BlobDownload from '@/components/table/BlobDownload.vue' +import QueryResults from '@/components/subset/Results.vue' export default { components: { + QueryResults, BlobDownload, EditTuple, TableHistory, @@ -142,6 +127,7 @@ export default { loadingData: false, loadingCount: false, loadingDelete: false, + loadingTable: false, addTupleDialog: false, editTupleDialog: false, total: 0, @@ -282,18 +268,11 @@ export default { }, watch: { version () { - this.loadCount() this.reload() - }, - table (newTable, oldTable) { - if (newTable !== oldTable && oldTable === null) { - this.loadProperties() - } } }, mounted () { - this.loadProperties() - this.loadCount() + this.reload() }, methods: { addTuple () { @@ -402,74 +381,10 @@ export default { } this.pickVersionDialog = false }, - loadProperties () { - if (!this.table || this.headers.length > 0) { - return - } - try { - this.headers = [] - this.table.columns.map((c) => { - return { - value: c.internal_name, - title: c.internal_name, - sortable: false - } - }).forEach(header => this.headers.push(header)) - this.dateColumns = this.table.columns.filter(c => (c.column_type === 'date' || c.column_type === 'timestamp')) - console.debug('date columns are', this.dateColumns) - } catch ({code}) { - const toast = useToastInstance() - toast.error(this.$t(code)) - } - this.loading = false - }, reload () { this.lastReload = new Date() - this.loadData({ page: this.options.page, itemsPerPage: this.options.itemsPerPage, sortBy: null}) - }, - loadCount() { - this.loadingCount = true - const tableService = useTableService() - tableService.getCount(this.$route.params.database_id, this.$route.params.table_id, (this.versionISO || this.lastReload.toISOString())) - .then((count) => { - this.total = count - this.loadingCount = false - }) - .catch(({code}) => { - this.loadingCount = false - const toast = useToastInstance() - toast.error(this.$t(code)) - }) - }, - loadData({ page, itemsPerPage, sortBy }) { - this.options.page = page - this.options.itemsPerPage = itemsPerPage - const tableService = useTableService() - this.loadingData = true - tableService.getData(this.$route.params.database_id, this.$route.params.table_id, (page - 1), itemsPerPage, (this.versionISO || this.lastReload.toISOString())) - .then((data) => { - this.rows = data.result.map((row) => { - for (const col in row) { - const column = this.table.columns.filter(c => c.internal_name === col)[0] - const columnDefinition = this.dateColumns.filter(c => c.internal_name === col) - if (columnDefinition.length > 0) { - if (columnDefinition[0].column_type === 'date') { - row[col] = formatDateUTC(row[col]) - } else if (columnDefinition[0].column_type === 'timestamp') { - row[col] = formatTimestampUTC(row[col]) - } - } - } - return row - }) - this.loadingData = false - }) - .catch(({code, message}) => { - this.error = true - this.loadingData = false - const toast = useToastInstance() - toast.error(this.$t(code) + ": " + message) - }) + this.$refs.queryResults.reExecute(Number(this.$route.params.table_id)) + this.$refs.queryResults.reExecuteCount(Number(this.$route.params.table_id)) }, isFileField (column) { return ['blob', 'longblob', 'mediumblob', 'tinyblob'].includes(column.column_type) -- GitLab From ebc88122448a597dfeacf33eb36d944fc1e383be Mon Sep 17 00:00:00 2001 From: Martin Weise <martin.weise@tuwien.ac.at> Date: Fri, 13 Dec 2024 15:10:13 +0100 Subject: [PATCH 3/5] Hotfix test --- .../java/at/tuwien/service/SubsetServiceIntegrationTest.java | 1 - 1 file changed, 1 deletion(-) 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 215e919844..13026ffafe 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 @@ -165,7 +165,6 @@ public class SubsetServiceIntegrationTest extends AbstractUnitTest { /* test */ final QueryDto response = queryService.findById(DATABASE_1_PRIVILEGED_DTO, queryId); assertEquals(QUERY_1_ID, response.getId()); - assertEquals(DATABASE_1_ID, response.getDatabaseId()); } protected List<QueryDto> findAll_generic(Boolean filterPersisted) throws SQLException, QueryNotFoundException, -- GitLab From e8c3269e4361eae6b77da7d55e8b141429f1e641 Mon Sep 17 00:00:00 2001 From: Martin Weise <martin.weise@tuwien.ac.at> Date: Fri, 13 Dec 2024 21:40:21 +0100 Subject: [PATCH 4/5] Fixed UI for private views and also library --- .../at/tuwien/endpoints/ViewEndpoint.java | 4 ++ dbrepo-ui/components/view/ViewToolbar.vue | 19 ++++++++ .../[database_id]/view/[view_id]/data.vue | 18 ++++--- lib/python/dbrepo/RestClient.py | 47 +++++-------------- lib/python/dbrepo/api/dto.py | 21 ++++----- lib/python/tests/test_unit_license.py | 4 +- 6 files changed, 58 insertions(+), 55 deletions(-) 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 001350246e..84b24b66c7 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 @@ -270,6 +270,10 @@ public class ViewEndpoint extends AbstractEndpoint { // TODO improve with a single operation that checks if user xyz has access to view abc final PrivilegedViewDto view = metadataServiceGateway.getViewById(databaseId, viewId); if (!view.getIsPublic()) { + if (principal == null) { + log.error("Failed to get data from view: unauthorized"); + throw new NotAllowedException("Failed to get data from view: unauthorized"); + } metadataServiceGateway.getAccess(databaseId, UserUtil.getId(principal)); } try { diff --git a/dbrepo-ui/components/view/ViewToolbar.vue b/dbrepo-ui/components/view/ViewToolbar.vue index c43b730397..fdbb47fe92 100644 --- a/dbrepo-ui/components/view/ViewToolbar.vue +++ b/dbrepo-ui/components/view/ViewToolbar.vue @@ -34,6 +34,7 @@ :text="$t('navigation.info')" :to="`/database/${$route.params.database_id}/view/${$route.params.view_id}/info`" /> <v-tab + v-if="canReadData" :text="$t('navigation.data')" :to="`/database/${$route.params.database_id}/view/${$route.params.view_id}/data`" /> </v-tabs> @@ -96,6 +97,24 @@ export default { roles () { return this.userStore.getRoles }, + hasReadAccess () { + if (!this.access) { + return false + } + return this.access.type === 'read' || this.access.type === 'write_own' || this.access.type === 'write_all' + }, + canReadData () { + if (!this.view) { + return false + } + if (this.view.is_public) { + return true + } + if (!this.user) { + return false + } + return this.view.owner.id === this.user.id || this.hasReadAccess + }, identifiers () { if (!this.view) { return [] diff --git a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/data.vue b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/data.vue index bc460da08b..b42972913e 100644 --- a/dbrepo-ui/pages/database/[database_id]/view/[view_id]/data.vue +++ b/dbrepo-ui/pages/database/[database_id]/view/[view_id]/data.vue @@ -1,5 +1,6 @@ <template> - <div> + <div + v-if="canReadData"> <ViewToolbar v-if="view" /> <v-toolbar @@ -7,7 +8,6 @@ :title="$t('toolbars.database.current')" flat> <v-btn - v-if="canDownload" :prepend-icon="$vuetify.display.lgAndUp ? 'mdi-download' : null" variant="flat" :loading="downloadLoading" @@ -85,18 +85,24 @@ export default { access () { return this.userStore.getAccess }, - canDownload () { + hasReadAccess () { + if (!this.access) { + return false + } + return this.access.type === 'read' || this.access.type === 'write_own' || this.access.type === 'write_all' + }, + canReadData () { if (!this.view) { return false } if (this.view.is_public) { return true } - if (!this.access) { + if (!this.user) { return false } - return this.access.type === 'read' || this.access.type === 'write_own' || this.access.type === 'write_all' - } + return this.view.owner.id === this.user.id || this.hasReadAccess + }, }, mounted () { this.reload() diff --git a/lib/python/dbrepo/RestClient.py b/lib/python/dbrepo/RestClient.py index b80daff7d0..16eb31a3e3 100644 --- a/lib/python/dbrepo/RestClient.py +++ b/lib/python/dbrepo/RestClient.py @@ -957,8 +957,7 @@ class RestClient: raise ResponseCodeError(f'Failed to delete view: response code: {response.status_code} is not ' f'202 (ACCEPTED): {response.text}') - def get_view_data(self, database_id: int, view_id: int, page: int = 0, size: int = 10, - df: bool = False) -> Result | DataFrame: + def get_view_data(self, database_id: int, view_id: int, page: int = 0, size: int = 10) -> DataFrame: """ Get data of a view in a database with given database id and view id. @@ -966,9 +965,8 @@ class RestClient: :param view_id: The view id. :param page: The result pagination number. Optional. Default: 0. :param size: The result pagination size. Optional. Default: 10. - :param df: If true, the result is returned as Pandas DataFrame. Optional. Default: False. - :returns: The result of the view query, if successful. + :returns: The view data, if successful. :raises MalformedError: If the payload is rejected by the service. :raises ForbiddenError: If something went wrong with the authorization. @@ -984,11 +982,7 @@ class RestClient: params.append(('size', size)) response = self._wrapper(method="get", url=url, params=params) if response.status_code == 200: - body = response.json() - res = Result.model_validate(body) - if df: - return DataFrame.from_records(res.result) - return res + return DataFrame.from_records(response.json()) if response.status_code == 400: raise MalformedError(f'Failed to get view data: {response.text}') if response.status_code == 403: @@ -1026,7 +1020,7 @@ class RestClient: f'200 (OK): {response.text}') def get_table_data(self, database_id: int, table_id: int, page: int = 0, size: int = 10, - timestamp: datetime.datetime = None, df: bool = False) -> Result | DataFrame: + timestamp: datetime.datetime = None) -> DataFrame: """ Get data of a table in a database with given database id and table id. @@ -1035,9 +1029,8 @@ class RestClient: :param page: The result pagination number. Optional. Default: 0. :param size: The result pagination size. Optional. Default: 10. :param timestamp: The query execution time. Optional. - :param df: If true, the result is returned as Pandas DataFrame. Optional. Default: False. - :returns: The result of the view query, if successful. + :returns: The table data, if successful. :raises MalformedError: If the payload is rejected by the service. :raises ForbiddenError: If something went wrong with the authorization. @@ -1054,11 +1047,7 @@ class RestClient: params.append(('timestamp', timestamp)) response = self._wrapper(method="get", url=url, params=params) if response.status_code == 200: - body = response.json() - res = Result.model_validate(body) - if df: - return DataFrame.from_records(res.result) - return res + return DataFrame.from_records(response.json()) if response.status_code == 400: raise MalformedError(f'Failed to get table data: {response.text}') if response.status_code == 403: @@ -1551,7 +1540,7 @@ class RestClient: f'201 (CREATED): {response.text}') def create_subset(self, database_id: int, query: str, page: int = 0, size: int = 10, - timestamp: datetime.datetime = None, df: bool = False) -> Result | DataFrame: + timestamp: datetime.datetime = None) -> DataFrame: """ Executes a SQL query in a database where the current user has at least read access with given database id. The result set can be paginated with setting page and size (both). Historic data can be queried by setting @@ -1562,7 +1551,6 @@ class RestClient: :param page: The result pagination number. Optional. Default: 0. :param size: The result pagination size. Optional. Default: 10. :param timestamp: The timestamp at which the data validity is set. Optional. Default: <current timestamp>. - :param df: If true, the result is returned as Pandas DataFrame. Optional. Default: False. :returns: The result set, if successful. @@ -1585,11 +1573,8 @@ class RestClient: response = self._wrapper(method="post", url=url, headers={"Accept": "application/json"}, payload=ExecuteQuery(statement=query)) if response.status_code == 201: - body = response.json() - res = Result.model_validate(body) - if df: - return DataFrame.from_records(res.result) - return res + logging.info(f'Created subset with id: {response.headers["X-Id"]}') + return DataFrame.from_records(response.json()) if response.status_code == 400: raise MalformedError(f'Failed to create subset: {response.text}') if response.status_code == 403: @@ -1605,8 +1590,7 @@ class RestClient: raise ResponseCodeError(f'Failed to create subset: response code: {response.status_code} is not ' f'201 (CREATED): {response.text}') - def get_subset_data(self, database_id: int, subset_id: int, page: int = 0, size: int = 10, - df: bool = False) -> Result | DataFrame: + def get_subset_data(self, database_id: int, subset_id: int, page: int = 0, size: int = 10) -> DataFrame: """ Re-executes a query in a database with given database id and query id. @@ -1615,9 +1599,8 @@ class RestClient: :param page: The result pagination number. Optional. Default: 0. :param size: The result pagination size. Optional. Default: 10. :param size: The result pagination size. Optional. Default: 10. - :param df: If true, the result is returned as Pandas DataFrame. Optional. Default: False. - :returns: The result set, if successful. + :returns: The subset data, if successful. :raises MalformedError: If the payload is rejected by the service. :raises ForbiddenError: If something went wrong with the authorization. @@ -1631,11 +1614,7 @@ class RestClient: url += f'?page={page}&size={size}' response = self._wrapper(method="get", url=url, headers=headers) if response.status_code == 200: - body = response.json() - res = Result.model_validate(body) - if df: - return DataFrame.from_records(res.result) - return res + return DataFrame.from_records(response.json()) if response.status_code == 400: raise MalformedError(f'Failed to get query data: {response.text}') if response.status_code == 403: @@ -1936,7 +1915,7 @@ class RestClient: :returns: List of licenses, if successful. """ - url = f'/api/database/license' + url = f'/api/license' response = self._wrapper(method="get", url=url) if response.status_code == 200: body = response.json() diff --git a/lib/python/dbrepo/api/dto.py b/lib/python/dbrepo/api/dto.py index 4de986e870..e54d1eba16 100644 --- a/lib/python/dbrepo/api/dto.py +++ b/lib/python/dbrepo/api/dto.py @@ -1,10 +1,11 @@ from __future__ import annotations +import datetime from dataclasses import field from enum import Enum -import datetime -from typing import List, Optional, Any, Annotated -from pydantic import BaseModel, ConfigDict, PlainSerializer, Field +from typing import List, Optional, Annotated + +from pydantic import BaseModel, PlainSerializer, Field Timestamp = Annotated[ datetime.datetime, PlainSerializer(lambda v: v.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z', return_type=str) @@ -638,12 +639,6 @@ class CreateView(BaseModel): is_public: bool -class Result(BaseModel): - result: Any - headers: Any - id: Optional[int] = None - - class ViewBrief(BaseModel): id: int database_id: int @@ -874,8 +869,8 @@ class DataType(BaseModel): display_name: str value: str documentation: str - is_quoted: bool - is_buildable: bool + is_quoted: bool + is_buildable: bool size_min: Optional[int] = None size_max: Optional[int] = None size_default: Optional[int] = None @@ -884,8 +879,8 @@ class DataType(BaseModel): d_max: Optional[int] = None d_default: Optional[int] = None d_required: Optional[bool] = None - data_hint: Optional[str] = None - type_hint: Optional[str] = None + data_hint: Optional[str] = None + type_hint: Optional[str] = None class Column(BaseModel): diff --git a/lib/python/tests/test_unit_license.py b/lib/python/tests/test_unit_license.py index 2efb613c42..7f2a52890e 100644 --- a/lib/python/tests/test_unit_license.py +++ b/lib/python/tests/test_unit_license.py @@ -12,7 +12,7 @@ class DatabaseUnitTest(unittest.TestCase): def test_get_licenses_empty_succeeds(self): with requests_mock.Mocker() as mock: # mock - mock.get('/api/database/license', json=[]) + mock.get('/api/license', json=[]) # test response = RestClient().get_licenses() self.assertEqual([], response) @@ -22,7 +22,7 @@ class DatabaseUnitTest(unittest.TestCase): exp = [License(identifier='CC-BY-4.0', uri='https://creativecommons.org/licenses/by/4.0/', description='The Creative Commons Attribution license allows re-distribution and re-use of a licensed work on the condition that the creator is appropriately credited.')] # mock - mock.get('/api/database/license', json=[exp[0].model_dump()]) + mock.get('/api/license', json=[exp[0].model_dump()]) # test response = RestClient().get_licenses() self.assertEqual(exp, response) -- GitLab From b3f4449c5fb39b9bc5e8b59e44dedae611651833 Mon Sep 17 00:00:00 2001 From: Martin Weise <martin.weise@tuwien.ac.at> Date: Fri, 13 Dec 2024 21:49:29 +0100 Subject: [PATCH 5/5] Updated tests --- lib/python/tests/test_unit_query.py | 47 +++++++++++++---------------- lib/python/tests/test_unit_table.py | 26 ++++++++-------- lib/python/tests/test_unit_view.py | 26 ++++++++-------- 3 files changed, 45 insertions(+), 54 deletions(-) diff --git a/lib/python/tests/test_unit_query.py b/lib/python/tests/test_unit_query.py index e1b326fa57..be0982d535 100644 --- a/lib/python/tests/test_unit_query.py +++ b/lib/python/tests/test_unit_query.py @@ -1,3 +1,4 @@ +import json import unittest import requests_mock @@ -6,25 +7,23 @@ import datetime from dbrepo.RestClient import RestClient from pandas import DataFrame -from dbrepo.api.dto import Result, Query, User, UserAttributes, QueryType -from dbrepo.api.exceptions import MalformedError, NotExistsError, ForbiddenError, QueryStoreError, \ - MetadataConsistencyError, AuthenticationError +from dbrepo.api.dto import Query, User, UserAttributes, QueryType +from dbrepo.api.exceptions import MalformedError, NotExistsError, ForbiddenError class QueryUnitTest(unittest.TestCase): def test_create_subset_succeeds(self): with requests_mock.Mocker() as mock: - exp = Result(result=[{'id': 1, 'username': 'foo'}, {'id': 2, 'username': 'bar'}], - headers=[{'id': 0, 'username': 1}], - id=None) + exp = [{'id': 1, 'username': 'foo'}, {'id': 2, 'username': 'bar'}] + df = DataFrame.from_records(json.dumps(exp)) # mock - mock.post('/api/database/1/subset', json=exp.model_dump(), status_code=201) + mock.post('/api/database/1/subset', json=json.dumps(exp), headers={'X-Id': '1'}, status_code=201) # test client = RestClient(username="a", password="b") response = client.create_subset(database_id=1, page=0, size=10, query="SELECT id, username FROM some_table WHERE id IN (1,2)") - self.assertEqual(exp, response) + self.assertTrue(DataFrame.equals(df, response)) def test_create_subset_malformed_fails(self): with requests_mock.Mocker() as mock: @@ -64,17 +63,16 @@ class QueryUnitTest(unittest.TestCase): def test_create_subset_not_auth_succeeds(self): with requests_mock.Mocker() as mock: - exp = Result(result=[{'id': 1, 'username': 'foo'}, {'id': 2, 'username': 'bar'}], - headers=[{'id': 0, 'username': 1}], - id=None) + exp = [{'id': 1, 'username': 'foo'}, {'id': 2, 'username': 'bar'}] + df = DataFrame.from_records(json.dumps(exp)) # mock - mock.post('/api/database/1/subset', json=exp.model_dump(), status_code=201) + mock.post('/api/database/1/subset', json=json.dumps(exp), headers={'X-Id': '1'}, status_code=201) # test client = RestClient() response = client.create_subset(database_id=1, page=0, size=10, query="SELECT id, username FROM some_table WHERE id IN (1,2)") - self.assertEqual(exp, response) + self.assertTrue(DataFrame.equals(df, response)) def test_find_query_succeeds(self): with requests_mock.Mocker() as mock: @@ -173,27 +171,24 @@ class QueryUnitTest(unittest.TestCase): def test_get_subset_data_succeeds(self): with requests_mock.Mocker() as mock: - exp = Result(result=[{'id': 1, 'username': 'foo'}, {'id': 2, 'username': 'bar'}], - headers=[{'id': 0, 'username': 1}], - id=6) + exp = [{'id': 1, 'username': 'foo'}, {'id': 2, 'username': 'bar'}] + df = DataFrame.from_records(json.dumps(exp)) # mock - mock.get('/api/database/1/subset/6/data', json=exp.model_dump()) + mock.get('/api/database/1/subset/6/data', json=json.dumps(exp)) # test response = RestClient().get_subset_data(database_id=1, subset_id=6) - self.assertEqual(exp, response) + self.assertTrue(DataFrame.equals(df, response)) def test_get_subset_data_dataframe_succeeds(self): with requests_mock.Mocker() as mock: - res = Result(result=[{'id': 1, 'username': 'foo'}, {'id': 2, 'username': 'bar'}], - headers=[{'id': 0, 'username': 1}], - id=6) - exp = DataFrame.from_records(res.model_dump()['result']) + exp = [{'id': 1, 'username': 'foo'}, {'id': 2, 'username': 'bar'}] + df = DataFrame.from_records(json.dumps(exp)) # mock - mock.get('/api/database/1/subset/6/data', json=res.model_dump()) + mock.get('/api/database/1/subset/6/data', json=json.dumps(exp)) # test - response = RestClient().get_subset_data(database_id=1, subset_id=6, df=True) - self.assertEqual(exp.shape, response.shape) - self.assertTrue(DataFrame.equals(exp, response)) + response = RestClient().get_subset_data(database_id=1, subset_id=6) + self.assertEqual(df.shape, response.shape) + self.assertTrue(DataFrame.equals(df, response)) def test_get_subset_data_not_allowed_fails(self): with requests_mock.Mocker() as mock: diff --git a/lib/python/tests/test_unit_table.py b/lib/python/tests/test_unit_table.py index 0be3a4a9fb..b6b7eacafc 100644 --- a/lib/python/tests/test_unit_table.py +++ b/lib/python/tests/test_unit_table.py @@ -1,3 +1,4 @@ +import json import unittest from json import dumps @@ -7,7 +8,7 @@ import datetime from dbrepo.RestClient import RestClient from pandas import DataFrame -from dbrepo.api.dto import Table, CreateTableConstraints, UserAttributes, User, Column, Constraints, ColumnType, Result, \ +from dbrepo.api.dto import Table, CreateTableConstraints, UserAttributes, User, Column, Constraints, ColumnType, \ Concept, Unit, TableStatistics, ColumnStatistic, PrimaryKey, TableMinimal, ColumnMinimal, TableBrief, UserBrief from dbrepo.api.exceptions import MalformedError, ForbiddenError, NotExistsError, NameExistsError, QueryStoreError, \ AuthenticationError, ExternalSystemError @@ -242,27 +243,24 @@ class TableUnitTest(unittest.TestCase): def test_get_table_data_succeeds(self): with requests_mock.Mocker() as mock: - exp = Result(result=[{'id': 1, 'username': 'foo'}, {'id': 2, 'username': 'bar'}], - headers=[{'id': 0, 'username': 1}], - id=None) + exp = [{'id': 1, 'username': 'foo'}, {'id': 2, 'username': 'bar'}] + df = DataFrame.from_records(json.dumps(exp)) # mock - mock.get('/api/database/1/table/9/data', json=exp.model_dump()) + mock.get('/api/database/1/table/9/data', json=json.dumps(exp)) # test response = RestClient().get_table_data(database_id=1, table_id=9) - self.assertEqual(exp, response) + self.assertTrue(DataFrame.equals(df, response)) def test_get_table_data_dataframe_succeeds(self): with requests_mock.Mocker() as mock: - res = Result(result=[{'id': 1, 'username': 'foo'}, {'id': 2, 'username': 'bar'}], - headers=[{'id': 0, 'username': 1}], - id=None) - exp = DataFrame.from_records(res.model_dump()['result']) + exp = [{'id': 1, 'username': 'foo'}, {'id': 2, 'username': 'bar'}] + df = DataFrame.from_records(json.dumps(exp)) # mock - mock.get('/api/database/1/table/9/data', json=res.model_dump()) + mock.get('/api/database/1/table/9/data', json=json.dumps(exp)) # test - response = RestClient().get_table_data(database_id=1, table_id=9, df=True) - self.assertEqual(exp.shape, response.shape) - self.assertTrue(DataFrame.equals(exp, response)) + response = RestClient().get_table_data(database_id=1, table_id=9) + self.assertEqual(df.shape, response.shape) + self.assertTrue(DataFrame.equals(df, response)) def test_get_table_data_malformed_fails(self): with requests_mock.Mocker() as mock: diff --git a/lib/python/tests/test_unit_view.py b/lib/python/tests/test_unit_view.py index 19a88be85a..30fa1d0cc1 100644 --- a/lib/python/tests/test_unit_view.py +++ b/lib/python/tests/test_unit_view.py @@ -1,3 +1,4 @@ +import json import unittest from json import dumps @@ -7,7 +8,7 @@ import datetime from dbrepo.RestClient import RestClient from pandas import DataFrame -from dbrepo.api.dto import UserAttributes, User, View, Result, ViewColumn, ColumnType +from dbrepo.api.dto import UserAttributes, User, View, ViewColumn, ColumnType from dbrepo.api.exceptions import ForbiddenError, NotExistsError, MalformedError, AuthenticationError @@ -211,27 +212,24 @@ class ViewUnitTest(unittest.TestCase): def test_get_view_data_succeeds(self): with requests_mock.Mocker() as mock: - exp = Result(result=[{'id': 1, 'username': 'foo'}, {'id': 2, 'username': 'bar'}], - headers=[{'id': 0, 'username': 1}], - id=None) + exp = [{'id': 1, 'username': 'foo'}, {'id': 2, 'username': 'bar'}] + df = DataFrame.from_records(json.dumps(exp)) # mock - mock.get('/api/database/1/view/3/data', json=exp.model_dump()) + mock.get('/api/database/1/view/3/data', json=json.dumps(exp)) # test response = RestClient().get_view_data(database_id=1, view_id=3) - self.assertEqual(exp, response) + self.assertTrue(DataFrame.equals(df, response)) def test_get_view_data_dataframe_succeeds(self): with requests_mock.Mocker() as mock: - res = Result(result=[{'id': 1, 'username': 'foo'}, {'id': 2, 'username': 'bar'}], - headers=[{'id': 0, 'username': 1}], - id=None) - exp = DataFrame.from_records(res.model_dump()['result']) + exp = [{'id': 1, 'username': 'foo'}, {'id': 2, 'username': 'bar'}] + df = DataFrame.from_records(json.dumps(exp)) # mock - mock.get('/api/database/1/view/3/data', json=res.model_dump()) + mock.get('/api/database/1/view/3/data', json=json.dumps(exp)) # test - response: DataFrame = RestClient().get_view_data(database_id=1, view_id=3, df=True) - self.assertEqual(exp.shape, response.shape) - self.assertTrue(DataFrame.equals(exp, response)) + response: DataFrame = RestClient().get_view_data(database_id=1, view_id=3) + self.assertEqual(df.shape, response.shape) + self.assertTrue(DataFrame.equals(df, response)) def test_get_view_data_malformed_fails(self): with requests_mock.Mocker() as mock: -- GitLab