From b3abb71558248a39bf529e143beab3d67417ebc9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Novotn=C3=BD?=
Date: Thu, 6 Feb 2025 09:54:58 +0100
Subject: [PATCH 01/22] doc: updated documentation
---
.github/release-drafter.yml | 4 ++--
README.md | 1 +
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml
index c030b7e208..8f56691d7d 100644
--- a/.github/release-drafter.yml
+++ b/.github/release-drafter.yml
@@ -5,7 +5,7 @@ latest: 'true'
prerelease: true
prerelease-identifier: 'beta'
categories:
- - title: '⁉️ Breaking changes'
+ - title: '⛓️💥 Breaking changes'
labels:
- 'breaking change'
- 'breaking'
@@ -28,7 +28,7 @@ autolabeler:
- label: 'bug'
title:
- '/^fix(\(.*\))?:/'
-change-template: '- $TITLE @$AUTHOR (#$NUMBER) $BODY'
+change-template: '- **$TITLE** @$AUTHOR (#$NUMBER) $BODY'
change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
filter-by-commitish: true
diff --git a/README.md b/README.md
index 7eaea3128d..18380bcfd3 100644
--- a/README.md
+++ b/README.md
@@ -211,6 +211,7 @@ In short, you need `~/.m2/toolchains.xml` in your home directory next to `~/.m2/
- **evita_external_api_graphql**: implementation of GraphQL API
- **evita_external_api_grpc**: implementation of gRPC API
- **client**: Java driver for client/server usage scenario
+ - **client_all_in_one**: Java driver for client/server usage scenario that includes repackaged all necessary dependencies so that it will not conflict with other dependencies in your project (unfortunately it's quite big due to gRPC and Armeria dependencies)
- **server**: gRPC server
- **shared**: shared classes between client & server (generated gRPC stubs)
- **evita_external_api_rest**: implementation of REST API
From 2eafa8b3c6566d1f2e9756e17519e512bb6ce9c0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Novotn=C3=BD?=
Date: Thu, 6 Feb 2025 10:11:44 +0100
Subject: [PATCH 02/22] doc: updated documentation
---
.github/workflows/ci-master.yml | 2 +-
README.md | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/ci-master.yml b/.github/workflows/ci-master.yml
index e603f14339..3eb91976f7 100644
--- a/.github/workflows/ci-master.yml
+++ b/.github/workflows/ci-master.yml
@@ -103,7 +103,7 @@ jobs:
uses: release-drafter/release-drafter@b1476f6e6eb133afa41ed8589daba6dc69b4d3f5 # v6.1.0
with:
version: ${{ steps.release_version.outputs.version }}
- publish: true
+ publish: false
latest: ${{ github.ref_name == 'master' && 'true' || 'legacy' }}
- name: Upload dist.zip to release
diff --git a/README.md b/README.md
index 18380bcfd3..a69b69a44c 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
examples and guides.
-
+
@@ -14,7 +14,7 @@ examples and guides.
-
+
From 714911fc07afc8d9d4ec5c9e336586e0fda36358 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Novotn=C3=BD?=
Date: Thu, 6 Feb 2025 10:55:37 +0100
Subject: [PATCH 03/22] doc: updated actual versions
---
.../en/07-advanced-features-on-developers-portal.md | 2 +-
documentation/user/en/get-started/query-our-dataset.md | 2 +-
documentation/user/en/get-started/run-evitadb.md | 8 ++++----
documentation/user/en/use/api/write-tests.md | 2 +-
documentation/user/en/use/connectors/java.md | 2 +-
evita_api/pom.xml | 2 +-
evita_common/pom.xml | 4 ++--
evita_db/pom.xml | 4 ++--
evita_engine/pom.xml | 4 ++--
evita_external_api/evita_external_api_core/pom.xml | 4 ++--
evita_external_api/evita_external_api_graphql/pom.xml | 4 ++--
.../evita_external_api_grpc/client/pom.xml | 4 ++--
.../evita_external_api_grpc/client_all_in_one/pom.xml | 2 +-
.../client_observability/pom.xml | 4 ++--
.../evita_external_api_grpc/server/pom.xml | 4 ++--
.../evita_external_api_grpc/shared/pom.xml | 4 ++--
evita_external_api/evita_external_api_lab/pom.xml | 4 ++--
.../evita_external_api_observability/pom.xml | 10 +++++-----
evita_external_api/evita_external_api_rest/pom.xml | 4 ++--
evita_external_api/evita_external_api_system/pom.xml | 4 ++--
evita_external_api/pom.xml | 4 ++--
evita_functional_tests/pom.xml | 4 ++--
evita_performance_tests/pom.xml | 4 ++--
evita_query/pom.xml | 4 ++--
evita_server/pom.xml | 4 ++--
evita_store/evita_store_common/pom.xml | 4 ++--
evita_store/evita_store_entity/pom.xml | 4 ++--
evita_store/evita_store_key_value/pom.xml | 4 ++--
evita_store/evita_store_server/pom.xml | 2 +-
evita_store/pom.xml | 4 ++--
evita_test_support/pom.xml | 4 ++--
jacoco/pom.xml | 4 ++--
pom.xml | 2 +-
33 files changed, 63 insertions(+), 63 deletions(-)
diff --git a/documentation/blog/en/07-advanced-features-on-developers-portal.md b/documentation/blog/en/07-advanced-features-on-developers-portal.md
index 1263953b6b..0cab1c8d99 100644
--- a/documentation/blog/en/07-advanced-features-on-developers-portal.md
+++ b/documentation/blog/en/07-advanced-features-on-developers-portal.md
@@ -51,7 +51,7 @@ Each code block is then rendered as an individual tab within the `io.evitadbevita_test_support
- 2024.10.0
+ 2025.1.0test
```
diff --git a/documentation/user/en/get-started/query-our-dataset.md b/documentation/user/en/get-started/query-our-dataset.md
index 36b12115e7..baa79f02a9 100644
--- a/documentation/user/en/get-started/query-our-dataset.md
+++ b/documentation/user/en/get-started/query-our-dataset.md
@@ -136,7 +136,7 @@ Open your Java IDE and add the following dependency to your project:
io.evitadbevita_java_driver
- 2024.10.0
+ 2025.1.0
```
diff --git a/documentation/user/en/get-started/run-evitadb.md b/documentation/user/en/get-started/run-evitadb.md
index af55852394..9f52e654b9 100644
--- a/documentation/user/en/get-started/run-evitadb.md
+++ b/documentation/user/en/get-started/run-evitadb.md
@@ -63,7 +63,7 @@ To integrate evitaDB into your project, use the following steps:
io.evitadbevita_db
- 2024.10.0
+ 2025.1.0pom
```
@@ -108,7 +108,7 @@ exception when you enable the corresponding API in evitaDB's configuration.
io.evitadbevita_external_api_grpc
- 2024.10.0
+ 2025.1.0pom
```
@@ -128,7 +128,7 @@ implementation 'io.evitadb:evita_external_api_grpc:2024.10.0'
io.evitadbevita_external_api_graphql
- 2024.10.0
+ 2025.1.0pom
```
@@ -148,7 +148,7 @@ implementation 'io.evitadb:evita_external_api_graphql:2024.10.0'
io.evitadbevita_external_api_rest
- 2024.10.0
+ 2025.1.0pom
```
diff --git a/documentation/user/en/use/api/write-tests.md b/documentation/user/en/use/api/write-tests.md
index fa5db80a46..6c914dc489 100644
--- a/documentation/user/en/use/api/write-tests.md
+++ b/documentation/user/en/use/api/write-tests.md
@@ -44,7 +44,7 @@ artifact into your project:
io.evitadbevita_test_support
- 2024.10.0
+ 2025.1.0test
```
diff --git a/documentation/user/en/use/connectors/java.md b/documentation/user/en/use/connectors/java.md
index a80436e1e4..07cb9242b4 100644
--- a/documentation/user/en/use/connectors/java.md
+++ b/documentation/user/en/use/connectors/java.md
@@ -30,7 +30,7 @@ In order to use a Java remote client you need only to add following dependency t
io.evitadbevita_java_driver
- 2024.10.0
+ 2025.1.0
```
diff --git a/evita_api/pom.xml b/evita_api/pom.xml
index 7033dc501d..ed170ff76a 100644
--- a/evita_api/pom.xml
+++ b/evita_api/pom.xml
@@ -31,7 +31,7 @@
io.evitadbevita_root
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOT
diff --git a/evita_common/pom.xml b/evita_common/pom.xml
index abf9c9e0c5..659edebdae 100644
--- a/evita_common/pom.xml
+++ b/evita_common/pom.xml
@@ -8,7 +8,7 @@
~ | __/\ V /| | || (_| | |_| | |_) |
~ \___| \_/ |_|\__\__,_|____/|____/
~
- ~ Copyright (c) 2023-2024
+ ~ Copyright (c) 2023-2025
~
~ Licensed under the Business Source License, Version 1.1 (the "License");
~ you may not use this file except in compliance with the License.
@@ -33,7 +33,7 @@
io.evitadbevita_root
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOT
diff --git a/evita_db/pom.xml b/evita_db/pom.xml
index 2e2469370c..82af2513e0 100644
--- a/evita_db/pom.xml
+++ b/evita_db/pom.xml
@@ -7,7 +7,7 @@
~ | __/\ V /| | || (_| | |_| | |_) |
~ \___| \_/ |_|\__\__,_|____/|____/
~
- ~ Copyright (c) 2023-2024
+ ~ Copyright (c) 2023-2025
~
~ Licensed under the Business Source License, Version 1.1 (the "License");
~ you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@
io.evitadbevita_root
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOT
diff --git a/evita_engine/pom.xml b/evita_engine/pom.xml
index c8cd251c4f..9a88bf9268 100644
--- a/evita_engine/pom.xml
+++ b/evita_engine/pom.xml
@@ -7,7 +7,7 @@
~ | __/\ V /| | || (_| | |_| | |_) |
~ \___| \_/ |_|\__\__,_|____/|____/
~
- ~ Copyright (c) 2023-2024
+ ~ Copyright (c) 2023-2025
~
~ Licensed under the Business Source License, Version 1.1 (the "License");
~ you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@
io.evitadbevita_root
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOT
diff --git a/evita_external_api/evita_external_api_core/pom.xml b/evita_external_api/evita_external_api_core/pom.xml
index 582e46df96..865fa786f8 100644
--- a/evita_external_api/evita_external_api_core/pom.xml
+++ b/evita_external_api/evita_external_api_core/pom.xml
@@ -7,7 +7,7 @@
~ | __/\ V /| | || (_| | |_| | |_) |
~ \___| \_/ |_|\__\__,_|____/|____/
~
- ~ Copyright (c) 2023-2024
+ ~ Copyright (c) 2023-2025
~
~ Licensed under the Business Source License, Version 1.1 (the "License");
~ you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@
io.evitadbevita_external_api
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOT
diff --git a/evita_external_api/evita_external_api_graphql/pom.xml b/evita_external_api/evita_external_api_graphql/pom.xml
index 3a6238690b..21b0d2a6a6 100644
--- a/evita_external_api/evita_external_api_graphql/pom.xml
+++ b/evita_external_api/evita_external_api_graphql/pom.xml
@@ -7,7 +7,7 @@
~ | __/\ V /| | || (_| | |_| | |_) |
~ \___| \_/ |_|\__\__,_|____/|____/
~
- ~ Copyright (c) 2023-2024
+ ~ Copyright (c) 2023-2025
~
~ Licensed under the Business Source License, Version 1.1 (the "License");
~ you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@
io.evitadbevita_external_api
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOT
diff --git a/evita_external_api/evita_external_api_grpc/client/pom.xml b/evita_external_api/evita_external_api_grpc/client/pom.xml
index 8def0c6015..d453f5d722 100644
--- a/evita_external_api/evita_external_api_grpc/client/pom.xml
+++ b/evita_external_api/evita_external_api_grpc/client/pom.xml
@@ -7,7 +7,7 @@
~ | __/\ V /| | || (_| | |_| | |_) |
~ \___| \_/ |_|\__\__,_|____/|____/
~
- ~ Copyright (c) 2023-2024
+ ~ Copyright (c) 2023-2025
~
~ Licensed under the Business Source License, Version 1.1 (the "License");
~ you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@
io.evitadbevita_root
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOT../../../pom.xml
diff --git a/evita_external_api/evita_external_api_grpc/client_all_in_one/pom.xml b/evita_external_api/evita_external_api_grpc/client_all_in_one/pom.xml
index 0c1ccd2b55..18d67a803c 100644
--- a/evita_external_api/evita_external_api_grpc/client_all_in_one/pom.xml
+++ b/evita_external_api/evita_external_api_grpc/client_all_in_one/pom.xml
@@ -31,7 +31,7 @@
io.evitadbevita_root
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOT../../../pom.xml
diff --git a/evita_external_api/evita_external_api_grpc/client_observability/pom.xml b/evita_external_api/evita_external_api_grpc/client_observability/pom.xml
index 89b5def5fa..e13871699e 100644
--- a/evita_external_api/evita_external_api_grpc/client_observability/pom.xml
+++ b/evita_external_api/evita_external_api_grpc/client_observability/pom.xml
@@ -7,7 +7,7 @@
~ | __/\ V /| | || (_| | |_| | |_) |
~ \___| \_/ |_|\__\__,_|____/|____/
~
- ~ Copyright (c) 2024
+ ~ Copyright (c) 2024-2025
~
~ Licensed under the Business Source License, Version 1.1 (the "License");
~ you may not use this file except in compliance with the License.
@@ -33,7 +33,7 @@
io.evitadbevita_root
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOT../../../pom.xml
diff --git a/evita_external_api/evita_external_api_grpc/server/pom.xml b/evita_external_api/evita_external_api_grpc/server/pom.xml
index 8753bb00b3..0adca376b7 100644
--- a/evita_external_api/evita_external_api_grpc/server/pom.xml
+++ b/evita_external_api/evita_external_api_grpc/server/pom.xml
@@ -7,7 +7,7 @@
~ | __/\ V /| | || (_| | |_| | |_) |
~ \___| \_/ |_|\__\__,_|____/|____/
~
- ~ Copyright (c) 2023-2024
+ ~ Copyright (c) 2023-2025
~
~ Licensed under the Business Source License, Version 1.1 (the "License");
~ you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@
io.evitadbevita_external_api
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOT../../pom.xml
diff --git a/evita_external_api/evita_external_api_grpc/shared/pom.xml b/evita_external_api/evita_external_api_grpc/shared/pom.xml
index 81f56777bb..b398d75ce9 100644
--- a/evita_external_api/evita_external_api_grpc/shared/pom.xml
+++ b/evita_external_api/evita_external_api_grpc/shared/pom.xml
@@ -7,7 +7,7 @@
~ | __/\ V /| | || (_| | |_| | |_) |
~ \___| \_/ |_|\__\__,_|____/|____/
~
- ~ Copyright (c) 2023-2024
+ ~ Copyright (c) 2023-2025
~
~ Licensed under the Business Source License, Version 1.1 (the "License");
~ you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@
io.evitadbevita_external_api
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOT../../pom.xml
diff --git a/evita_external_api/evita_external_api_lab/pom.xml b/evita_external_api/evita_external_api_lab/pom.xml
index 719aef3f24..de55db3a5c 100644
--- a/evita_external_api/evita_external_api_lab/pom.xml
+++ b/evita_external_api/evita_external_api_lab/pom.xml
@@ -6,7 +6,7 @@
~ | __/\ V /| | || (_| | |_| | |_) |
~ \___| \_/ |_|\__\__,_|____/|____/
~
- ~ Copyright (c) 2023-2024
+ ~ Copyright (c) 2023-2025
~
~ Licensed under the Business Source License, Version 1.1 (the "License");
~ you may not use this file except in compliance with the License.
@@ -30,7 +30,7 @@
io.evitadbevita_external_api
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOT
diff --git a/evita_external_api/evita_external_api_observability/pom.xml b/evita_external_api/evita_external_api_observability/pom.xml
index 6ead31c204..eb7ffaeeb0 100644
--- a/evita_external_api/evita_external_api_observability/pom.xml
+++ b/evita_external_api/evita_external_api_observability/pom.xml
@@ -7,7 +7,7 @@
~ | __/\ V /| | || (_| | |_| | |_) |
~ \___| \_/ |_|\__\__,_|____/|____/
~
- ~ Copyright (c) 2023-2024
+ ~ Copyright (c) 2023-2025
~
~ Licensed under the Business Source License, Version 1.1 (the "License");
~ you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@
io.evitadbevita_external_api
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOT
@@ -129,19 +129,19 @@
io.evitadbevita_external_api_grpc
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOTcompileio.evitadbevita_external_api_graphql
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOTcompileio.evitadbevita_external_api_rest
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOTcompile
diff --git a/evita_external_api/evita_external_api_rest/pom.xml b/evita_external_api/evita_external_api_rest/pom.xml
index 48473b3796..0374c0fd25 100644
--- a/evita_external_api/evita_external_api_rest/pom.xml
+++ b/evita_external_api/evita_external_api_rest/pom.xml
@@ -7,7 +7,7 @@
~ | __/\ V /| | || (_| | |_| | |_) |
~ \___| \_/ |_|\__\__,_|____/|____/
~
- ~ Copyright (c) 2023-2024
+ ~ Copyright (c) 2023-2025
~
~ Licensed under the Business Source License, Version 1.1 (the "License");
~ you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@
io.evitadbevita_external_api
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOT
diff --git a/evita_external_api/evita_external_api_system/pom.xml b/evita_external_api/evita_external_api_system/pom.xml
index b09ad3b447..bb1f9d6a51 100644
--- a/evita_external_api/evita_external_api_system/pom.xml
+++ b/evita_external_api/evita_external_api_system/pom.xml
@@ -7,7 +7,7 @@
~ | __/\ V /| | || (_| | |_| | |_) |
~ \___| \_/ |_|\__\__,_|____/|____/
~
- ~ Copyright (c) 2023-2024
+ ~ Copyright (c) 2023-2025
~
~ Licensed under the Business Source License, Version 1.1 (the "License");
~ you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@
io.evitadbevita_external_api
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOT
diff --git a/evita_external_api/pom.xml b/evita_external_api/pom.xml
index d96b0d7051..67962bb26c 100644
--- a/evita_external_api/pom.xml
+++ b/evita_external_api/pom.xml
@@ -7,7 +7,7 @@
~ | __/\ V /| | || (_| | |_| | |_) |
~ \___| \_/ |_|\__\__,_|____/|____/
~
- ~ Copyright (c) 2023-2024
+ ~ Copyright (c) 2023-2025
~
~ Licensed under the Business Source License, Version 1.1 (the "License");
~ you may not use this file except in compliance with the License.
@@ -30,7 +30,7 @@
io.evitadbevita_root
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOT
diff --git a/evita_functional_tests/pom.xml b/evita_functional_tests/pom.xml
index dce782e207..dee4e958fc 100644
--- a/evita_functional_tests/pom.xml
+++ b/evita_functional_tests/pom.xml
@@ -7,7 +7,7 @@
~ | __/\ V /| | || (_| | |_| | |_) |
~ \___| \_/ |_|\__\__,_|____/|____/
~
- ~ Copyright (c) 2023-2024
+ ~ Copyright (c) 2023-2025
~
~ Licensed under the Business Source License, Version 1.1 (the "License");
~ you may not use this file except in compliance with the License.
@@ -30,7 +30,7 @@
io.evitadbevita_root
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOT
diff --git a/evita_performance_tests/pom.xml b/evita_performance_tests/pom.xml
index 40c2095e9d..d57c02f633 100644
--- a/evita_performance_tests/pom.xml
+++ b/evita_performance_tests/pom.xml
@@ -7,7 +7,7 @@
~ | __/\ V /| | || (_| | |_| | |_) |
~ \___| \_/ |_|\__\__,_|____/|____/
~
- ~ Copyright (c) 2023-2024
+ ~ Copyright (c) 2023-2025
~
~ Licensed under the Business Source License, Version 1.1 (the "License");
~ you may not use this file except in compliance with the License.
@@ -30,7 +30,7 @@
io.evitadbevita_root
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOT
diff --git a/evita_query/pom.xml b/evita_query/pom.xml
index ca87e5ff3c..3a5e5354d9 100644
--- a/evita_query/pom.xml
+++ b/evita_query/pom.xml
@@ -7,7 +7,7 @@
~ | __/\ V /| | || (_| | |_| | |_) |
~ \___| \_/ |_|\__\__,_|____/|____/
~
- ~ Copyright (c) 2023-2024
+ ~ Copyright (c) 2023-2025
~
~ Licensed under the Business Source License, Version 1.1 (the "License");
~ you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@
io.evitadbevita_root
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOT4.9.2
diff --git a/evita_server/pom.xml b/evita_server/pom.xml
index b562ead0ab..3c28fb8abb 100644
--- a/evita_server/pom.xml
+++ b/evita_server/pom.xml
@@ -7,7 +7,7 @@
~ | __/\ V /| | || (_| | |_| | |_) |
~ \___| \_/ |_|\__\__,_|____/|____/
~
- ~ Copyright (c) 2023-2024
+ ~ Copyright (c) 2023-2025
~
~ Licensed under the Business Source License, Version 1.1 (the "License");
~ you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@
io.evitadbevita_root
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOT
diff --git a/evita_store/evita_store_common/pom.xml b/evita_store/evita_store_common/pom.xml
index 60dc58e8c4..a595a7476a 100644
--- a/evita_store/evita_store_common/pom.xml
+++ b/evita_store/evita_store_common/pom.xml
@@ -7,7 +7,7 @@
~ | __/\ V /| | || (_| | |_| | |_) |
~ \___| \_/ |_|\__\__,_|____/|____/
~
- ~ Copyright (c) 2023-2024
+ ~ Copyright (c) 2023-2025
~
~ Licensed under the Business Source License, Version 1.1 (the "License");
~ you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@
io.evitadbevita_store
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOT
diff --git a/evita_store/evita_store_entity/pom.xml b/evita_store/evita_store_entity/pom.xml
index 3293069212..964fed115c 100644
--- a/evita_store/evita_store_entity/pom.xml
+++ b/evita_store/evita_store_entity/pom.xml
@@ -7,7 +7,7 @@
~ | __/\ V /| | || (_| | |_| | |_) |
~ \___| \_/ |_|\__\__,_|____/|____/
~
- ~ Copyright (c) 2023-2024
+ ~ Copyright (c) 2023-2025
~
~ Licensed under the Business Source License, Version 1.1 (the "License");
~ you may not use this file except in compliance with the License.
@@ -32,7 +32,7 @@
io.evitadbevita_store
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOT
diff --git a/evita_store/evita_store_key_value/pom.xml b/evita_store/evita_store_key_value/pom.xml
index 04df1c4700..f5e06ea055 100644
--- a/evita_store/evita_store_key_value/pom.xml
+++ b/evita_store/evita_store_key_value/pom.xml
@@ -7,7 +7,7 @@
~ | __/\ V /| | || (_| | |_| | |_) |
~ \___| \_/ |_|\__\__,_|____/|____/
~
- ~ Copyright (c) 2023-2024
+ ~ Copyright (c) 2023-2025
~
~ Licensed under the Business Source License, Version 1.1 (the "License");
~ you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@
io.evitadbevita_store
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOT
diff --git a/evita_store/evita_store_server/pom.xml b/evita_store/evita_store_server/pom.xml
index 9140fe2b2a..4a07ef2f35 100644
--- a/evita_store/evita_store_server/pom.xml
+++ b/evita_store/evita_store_server/pom.xml
@@ -31,7 +31,7 @@
io.evitadbevita_store
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOT
diff --git a/evita_store/pom.xml b/evita_store/pom.xml
index edba902107..38ca909fa9 100644
--- a/evita_store/pom.xml
+++ b/evita_store/pom.xml
@@ -7,7 +7,7 @@
~ | __/\ V /| | || (_| | |_| | |_) |
~ \___| \_/ |_|\__\__,_|____/|____/
~
- ~ Copyright (c) 2023-2024
+ ~ Copyright (c) 2023-2025
~
~ Licensed under the Business Source License, Version 1.1 (the "License");
~ you may not use this file except in compliance with the License.
@@ -30,7 +30,7 @@
io.evitadbevita_root
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOT
diff --git a/evita_test_support/pom.xml b/evita_test_support/pom.xml
index 8078d42bf6..6ce1cc1a23 100644
--- a/evita_test_support/pom.xml
+++ b/evita_test_support/pom.xml
@@ -7,7 +7,7 @@
~ | __/\ V /| | || (_| | |_| | |_) |
~ \___| \_/ |_|\__\__,_|____/|____/
~
- ~ Copyright (c) 2023-2024
+ ~ Copyright (c) 2023-2025
~
~ Licensed under the Business Source License, Version 1.1 (the "License");
~ you may not use this file except in compliance with the License.
@@ -31,7 +31,7 @@
io.evitadbevita_root
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOT
diff --git a/jacoco/pom.xml b/jacoco/pom.xml
index 439ac4b16f..5b1dc4bcd3 100644
--- a/jacoco/pom.xml
+++ b/jacoco/pom.xml
@@ -7,7 +7,7 @@
~ | __/\ V /| | || (_| | |_| | |_) |
~ \___| \_/ |_|\__\__,_|____/|____/
~
- ~ Copyright (c) 2023-2024
+ ~ Copyright (c) 2023-2025
~
~ Licensed under the Business Source License, Version 1.1 (the "License");
~ you may not use this file except in compliance with the License.
@@ -30,7 +30,7 @@
io.evitadbevita_root
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOT
diff --git a/pom.xml b/pom.xml
index 1a92406b16..47812f9391 100644
--- a/pom.xml
+++ b/pom.xml
@@ -25,7 +25,7 @@
4.0.0io.evitadbevita_root
- 2025.1-SNAPSHOT
+ 2025.2-SNAPSHOTpomevitaDB - Root & modules aggregatorhttps://evitadb.io
From dfcd7eb2e197cd6b3e7086b2f87b26f76d799ff7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Novotn=C3=BD?=
Date: Thu, 6 Feb 2025 12:41:22 +0100
Subject: [PATCH 04/22] fix: ClassCastException in query purifier
---
.../grpc/services/EvitaSessionService.java | 12 ++++++---
.../main/java/io/evitadb/api/query/Query.java | 27 ++++++++++++-------
2 files changed, 25 insertions(+), 14 deletions(-)
diff --git a/evita_external_api/evita_external_api_grpc/server/src/main/java/io/evitadb/externalApi/grpc/services/EvitaSessionService.java b/evita_external_api/evita_external_api_grpc/server/src/main/java/io/evitadb/externalApi/grpc/services/EvitaSessionService.java
index 3650ba285b..548d52777d 100644
--- a/evita_external_api/evita_external_api_grpc/server/src/main/java/io/evitadb/externalApi/grpc/services/EvitaSessionService.java
+++ b/evita_external_api/evita_external_api_grpc/server/src/main/java/io/evitadb/externalApi/grpc/services/EvitaSessionService.java
@@ -30,6 +30,7 @@
import io.evitadb.api.EvitaSessionContract;
import io.evitadb.api.exception.SessionNotFoundException;
import io.evitadb.api.file.FileForFetch;
+import io.evitadb.api.query.Constraint;
import io.evitadb.api.query.HeadConstraint;
import io.evitadb.api.query.Query;
import io.evitadb.api.query.head.Head;
@@ -1519,23 +1520,26 @@ public void getTransactionId(Empty request, StreamObserver {
+ private static class LabelAppender implements UnaryOperator {
private final Label[] labels;
private boolean appended = false;
@Override
- public HeadConstraint apply(HeadConstraint constraint) {
+ public Constraint apply(Constraint constraint) {
if (this.appended) {
return constraint;
- } else {
+ } else if (constraint instanceof HeadConstraint headConstraint) {
this.appended = true;
final List constraints = new ArrayList<>(labels.length + 1);
- constraints.add(constraint);
+ constraints.add(headConstraint);
Arrays.stream(this.labels).filter(Objects::nonNull).forEach(constraints::add);
//noinspection DataFlowIssue
return head(constraints.toArray(HeadConstraint[]::new));
+ } else {
+ throw new UnsupportedOperationException("Cannot append labels to a non-head constraint.");
}
}
diff --git a/evita_query/src/main/java/io/evitadb/api/query/Query.java b/evita_query/src/main/java/io/evitadb/api/query/Query.java
index 72c8a6f75c..316a409398 100644
--- a/evita_query/src/main/java/io/evitadb/api/query/Query.java
+++ b/evita_query/src/main/java/io/evitadb/api/query/Query.java
@@ -6,7 +6,7 @@
* | __/\ V /| | || (_| | |_| | |_) |
* \___| \_/ |_|\__\__,_|____/|____/
*
- * Copyright (c) 2023-2024
+ * Copyright (c) 2023-2025
*
* Licensed under the Business Source License, Version 1.1 (the "License");
* you may not use this file except in compliance with the License.
@@ -197,12 +197,13 @@ public Query normalizeQuery() {
* @deprecated use {@link #normalizeQuery(UnaryOperator, UnaryOperator, UnaryOperator, UnaryOperator)}, this method
* is here only to maintain backward compatibility
*/
+ @SuppressWarnings("rawtypes")
@Deprecated
@Nonnull
public Query normalizeQuery(
- @Nullable UnaryOperator filterConstraintTranslator,
- @Nullable UnaryOperator orderConstraintTranslator,
- @Nullable UnaryOperator requireConstraintTranslator
+ @Nullable UnaryOperator filterConstraintTranslator,
+ @Nullable UnaryOperator orderConstraintTranslator,
+ @Nullable UnaryOperator requireConstraintTranslator
) {
return normalizeQuery(
null,
@@ -218,19 +219,22 @@ public Query normalizeQuery(
* from the query, all query containers that are {@link ConstraintContainer#isNecessary()} are removed
* and their contents are propagated to their parent.
*/
+ /* we need to use raw types because constraint of type A might contain constraints of type B */
+ /* i.e. require constraint might contain filtering constraints etc. */
+ @SuppressWarnings("rawtypes")
@Nonnull
public Query normalizeQuery(
- @Nullable UnaryOperator headConstraintTranslator,
- @Nullable UnaryOperator filterConstraintTranslator,
- @Nullable UnaryOperator orderConstraintTranslator,
- @Nullable UnaryOperator requireConstraintTranslator
+ @Nullable UnaryOperator headConstraintTranslator,
+ @Nullable UnaryOperator filterConstraintTranslator,
+ @Nullable UnaryOperator orderConstraintTranslator,
+ @Nullable UnaryOperator requireConstraintTranslator
) {
// avoid costly normalization on already normalized query
if (normalized) {
return this;
}
- final HeadConstraint normalizedHead = this.head == null ? null : purify(this.head, headConstraintTranslator);
+ final HeadConstraint normalizedHead = this.head == null ? null : (HeadConstraint) purify(this.head, headConstraintTranslator);
final FilterBy normalizedFilter = this.filterBy == null ? null : (FilterBy) purify(this.filterBy, filterConstraintTranslator);
final OrderBy normalizedOrder = this.orderBy == null ? null : (OrderBy) purify(this.orderBy, orderConstraintTranslator);
final Require normalizedRequire = this.require == null ? null : (Require) purify(this.require, requireConstraintTranslator);
@@ -263,8 +267,11 @@ public StringWithParameters toStringWithParameterExtraction() {
return PrettyPrintingVisitor.toStringWithParameterExtraction(this);
}
+ /* we need to use raw types because constraint of type A might contain constraints of type B */
+ /* i.e. require constraint might contain filtering constraints etc. */
@Nullable
- private static > T purify(@Nonnull T constraint, @Nullable UnaryOperator translator) {
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ private static Constraint purify(@Nonnull Constraint constraint, @Nullable UnaryOperator translator) {
return QueryPurifierVisitor.purify(constraint, translator);
}
From 1098d6eaa525918bfb8e3a14b5b7b0a888838693 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Novotn=C3=BD?=
Date: Thu, 6 Feb 2025 12:46:31 +0100
Subject: [PATCH 05/22] fix: head constraint with label in evitaQL cannot be
parsed
---
.../query/parser/visitor/EvitaQLQueryVisitor.java | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/evita_query/src/main/java/io/evitadb/api/query/parser/visitor/EvitaQLQueryVisitor.java b/evita_query/src/main/java/io/evitadb/api/query/parser/visitor/EvitaQLQueryVisitor.java
index 95716f8f8f..6b933e6c8e 100644
--- a/evita_query/src/main/java/io/evitadb/api/query/parser/visitor/EvitaQLQueryVisitor.java
+++ b/evita_query/src/main/java/io/evitadb/api/query/parser/visitor/EvitaQLQueryVisitor.java
@@ -6,7 +6,7 @@
* | __/\ V /| | || (_| | |_| | |_) |
* \___| \_/ |_|\__\__,_|____/|____/
*
- * Copyright (c) 2023
+ * Copyright (c) 2023-2025
*
* Licensed under the Business Source License, Version 1.1 (the "License");
* you may not use this file except in compliance with the License.
@@ -65,13 +65,13 @@ public Query visitQuery(@Nonnull EvitaQLParser.QueryContext ctx) {
.map(con -> con.accept(constraintVisitor))
.collect(Collectors.toList());
- final Collection collectionConstraint = findCollectionConstraint(ctx, constraints);
+ final HeadConstraint headConstraint = findHeadConstraint(ctx, constraints);
final FilterBy filterByConstraint = findFilterByConstraint(ctx, constraints);
final OrderBy orderByConstraint = findOrderByConstraint(ctx, constraints);
final Require requireConstraint = findRequireConstraint(ctx, constraints);
return Query.query(
- collectionConstraint,
+ headConstraint,
filterByConstraint,
orderByConstraint,
requireConstraint
@@ -88,7 +88,7 @@ public Query visitQuery(@Nonnull EvitaQLParser.QueryContext ctx) {
* @throws EvitaSyntaxException if no appropriated constraint found
*/
@Nullable
- protected Collection findCollectionConstraint(@Nonnull ParserRuleContext ctx, @Nonnull List> topLevelConstraints) {
+ protected HeadConstraint findHeadConstraint(@Nonnull ParserRuleContext ctx, @Nonnull List> topLevelConstraints) {
final List> headConstraints = topLevelConstraints.stream()
.filter(HeadConstraint.class::isInstance)
.toList();
@@ -99,10 +99,10 @@ protected Collection findCollectionConstraint(@Nonnull ParserRuleContext ctx, @N
if ((headConstraints.size() > 1) || !(headConstraints.get(0) instanceof Collection)) {
throw new EvitaSyntaxException(
ctx,
- "Query can have only one top level head constraint -> `collection`."
+ "Query can have only one top level head constraint."
);
}
- return (Collection) headConstraints.get(0);
+ return (HeadConstraint) headConstraints.get(0);
}
/**
From f66c5962821790ee29241c61b171173fec8cc293 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Novotn=C3=BD?=
Date: Thu, 6 Feb 2025 12:56:53 +0100
Subject: [PATCH 06/22] fix: added check for number of total bytes to write
---
.../main/java/io/evitadb/store/traffic/DiskRingBuffer.java | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/evita_store/evita_store_server/src/main/java/io/evitadb/store/traffic/DiskRingBuffer.java b/evita_store/evita_store_server/src/main/java/io/evitadb/store/traffic/DiskRingBuffer.java
index dde70d21f4..e49aafaa81 100644
--- a/evita_store/evita_store_server/src/main/java/io/evitadb/store/traffic/DiskRingBuffer.java
+++ b/evita_store/evita_store_server/src/main/java/io/evitadb/store/traffic/DiskRingBuffer.java
@@ -666,6 +666,10 @@ private void writeDataToFileChannel(@Nonnull ByteBuffer memoryByteBuffer, int to
* used to adjust the position of the ring buffer tail.
*/
private void updateSessionLocations(int totalBytesToWrite) throws IOException {
+ if (totalBytesToWrite == 0) {
+ // no bytes to write, nothing to update
+ return;
+ }
final long newTail = this.ringBufferTail + totalBytesToWrite;
SessionLocation head = this.sessionLocations.peekFirst();
while (head != null) {
From 2ae764f89559adea838f9eb7c7d0cc205bfd458c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Novotn=C3=BD?=
Date: Thu, 6 Feb 2025 14:12:46 +0100
Subject: [PATCH 07/22] fix: NullPointerException in postponed index lambda
---
.../evitadb/store/traffic/DiskRingBuffer.java | 29 +++++++++----------
1 file changed, 13 insertions(+), 16 deletions(-)
diff --git a/evita_store/evita_store_server/src/main/java/io/evitadb/store/traffic/DiskRingBuffer.java b/evita_store/evita_store_server/src/main/java/io/evitadb/store/traffic/DiskRingBuffer.java
index e49aafaa81..a51028f581 100644
--- a/evita_store/evita_store_server/src/main/java/io/evitadb/store/traffic/DiskRingBuffer.java
+++ b/evita_store/evita_store_server/src/main/java/io/evitadb/store/traffic/DiskRingBuffer.java
@@ -113,7 +113,7 @@ public class DiskRingBuffer {
* Contains set of postponed index updates, that were captured during initial session index creation.
* The reference is empty when no indexing is being done (i.e. almost always).
*/
- private final AtomicReference> postponedIndexUpdates = new AtomicReference<>();
+ private final AtomicReference>> postponedIndexUpdates = new AtomicReference<>();
/**
* Transaction used for writing to the session index.
*/
@@ -410,19 +410,16 @@ public void sessionWritten(
ofNullable(this.postponedIndexUpdates.get())
.ifPresent(
postponedUpdates -> postponedUpdates.add(
- () -> {
- final TrafficRecordingIndex newIndex = this.sessionIndex.get();
- newIndex.setupSession(
- sessionLocation,
- sessionId,
- created,
- durationInMillis,
- fetchCount,
- bytesFetchedTotal,
- recordingTypes,
- labels
- );
- }
+ theIndex -> theIndex.setupSession(
+ sessionLocation,
+ sessionId,
+ created,
+ durationInMillis,
+ fetchCount,
+ bytesFetchedTotal,
+ recordingTypes,
+ labels
+ )
)
);
}
@@ -555,10 +552,10 @@ public void indexData(
}
// index is ready, process postponed updates
- final Deque theLambdasToExecute = this.postponedIndexUpdates.getAndSet(null);
+ final Deque> theLambdasToExecute = this.postponedIndexUpdates.getAndSet(null);
notNull(theLambdasToExecute, "Postponed index updates are null. This is not expected!");
theLambdasToExecute.forEach(lambda -> {
- lambda.run();
+ lambda.accept(index);
this.indexedSessions.incrementAndGet();
});
From bb1135e423a357adcf9d4c4a536047c20fba735f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Novotn=C3=BD?=
Date: Thu, 6 Feb 2025 14:21:20 +0100
Subject: [PATCH 08/22] fix: More generic task automatic name detection
---
.../core/async/ObservableThreadExecutor.java | 20 +++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
diff --git a/evita_engine/src/main/java/io/evitadb/core/async/ObservableThreadExecutor.java b/evita_engine/src/main/java/io/evitadb/core/async/ObservableThreadExecutor.java
index 4d18110865..a9d41e3b52 100644
--- a/evita_engine/src/main/java/io/evitadb/core/async/ObservableThreadExecutor.java
+++ b/evita_engine/src/main/java/io/evitadb/core/async/ObservableThreadExecutor.java
@@ -512,7 +512,15 @@ private static class ObservableRunnable implements Runnable, ObservableTask {
public ObservableRunnable(@Nonnull Runnable delegate, long timeoutInMilliseconds) {
final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
- this.name = stackTrace.length > 1 ? stackTrace[1].toString() : "Unknown";
+ // pick first name that doesn't contain Observable in the class name
+ String name = "Unknown";
+ for (StackTraceElement element : stackTrace) {
+ if (!element.getClassName().contains("Observable")) {
+ name = element.toString();
+ break;
+ }
+ }
+ this.name = name;
this.delegate = delegate;
this.timedOutAt = System.currentTimeMillis() + timeoutInMilliseconds;
}
@@ -586,7 +594,15 @@ private static class ObservableCallable implements Callable, ObservableTas
public ObservableCallable(@Nonnull Callable delegate, long timeout) {
final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
- this.name = stackTrace.length > 1 ? stackTrace[1].toString() : "Unknown";
+ // pick first name that doesn't contain Observable in the class name
+ String name = "Unknown";
+ for (StackTraceElement element : stackTrace) {
+ if (!element.getClassName().contains("Observable")) {
+ name = element.toString();
+ break;
+ }
+ }
+ this.name = name;
this.delegate = delegate;
this.timedOutAt = System.currentTimeMillis() + timeout;
}
From 7ff76d62d9c5dbda95c9ed3b3060678e124171b1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?=
Date: Thu, 6 Feb 2025 14:51:29 +0100
Subject: [PATCH 09/22] fix: cannot use facetXXXGroupsXXX for reference without
group type (i.e. with no parameters)
Refs: #798
---
.../facet/facet-groups-conjunction.graphql | 2 +-
.../facet/facet-groups-conjunction.rest | 2 +-
.../constraint/ConstraintSchemaBuilder.java | 21 +++++--------------
.../constraint/ConstraintResolver.java | 13 ++++++++----
.../GraphQLConstraintSchemaBuilder.java | 9 +++++---
.../OpenApiConstraintSchemaBuilder.java | 9 +++++---
.../query/ConstraintToJsonConverter.java | 7 ++++++-
7 files changed, 34 insertions(+), 29 deletions(-)
diff --git a/documentation/user/en/query/requirements/examples/facet/facet-groups-conjunction.graphql b/documentation/user/en/query/requirements/examples/facet/facet-groups-conjunction.graphql
index 2bab039318..96151eb7bb 100644
--- a/documentation/user/en/query/requirements/examples/facet/facet-groups-conjunction.graphql
+++ b/documentation/user/en/query/requirements/examples/facet/facet-groups-conjunction.graphql
@@ -16,7 +16,7 @@
]
},
require: {
- facetGroupsGroupsConjunction: { }
+ facetGroupsGroupsConjunction: true
}
) {
extraResults {
diff --git a/documentation/user/en/query/requirements/examples/facet/facet-groups-conjunction.rest b/documentation/user/en/query/requirements/examples/facet/facet-groups-conjunction.rest
index 168cda21ac..95ada27356 100644
--- a/documentation/user/en/query/requirements/examples/facet/facet-groups-conjunction.rest
+++ b/documentation/user/en/query/requirements/examples/facet/facet-groups-conjunction.rest
@@ -27,6 +27,6 @@ POST /rest/evita/Product/query
}
}
},
- "facetGroupsGroupsConjunction" : { }
+ "facetGroupsGroupsConjunction" : true
}
}
\ No newline at end of file
diff --git a/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/api/catalog/dataApi/builder/constraint/ConstraintSchemaBuilder.java b/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/api/catalog/dataApi/builder/constraint/ConstraintSchemaBuilder.java
index 19f65e9cf3..86135510b1 100644
--- a/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/api/catalog/dataApi/builder/constraint/ConstraintSchemaBuilder.java
+++ b/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/api/catalog/dataApi/builder/constraint/ConstraintSchemaBuilder.java
@@ -191,14 +191,7 @@ public SIMPLE_TYPE build(@Nonnull DataLocator rootDataLocator, @Nonnull Constrai
*/
@Nonnull
protected SIMPLE_TYPE build(@Nonnull ConstraintBuildContext buildContext, @Nonnull ConstraintDescriptor constraintDescriptor) {
- final SIMPLE_TYPE simpleType = buildConstraintValue(buildContext, constraintDescriptor, null);
- // note: if nullable support is required at root level, the null value can be propagated up, but all cases that
- // expect nonnull value must be handled
- Assert.isPremiseValid(
- simpleType != null,
- () -> createSchemaBuildingError("Null constraint value as root constraint value not supported yet.")
- );
- return simpleType;
+ return buildConstraintValue(buildContext, constraintDescriptor, null);
}
@@ -745,12 +738,8 @@ protected OBJECT_FIELD buildFieldFromConstraintDescriptor(@Nonnull ConstraintBui
return null;
}
- final SIMPLE_TYPE constraintValue = buildConstraintValue(buildContext, constraintDescriptor, valueTypeSupplier);
- if (constraintValue == null) {
- // no value (no usable parameter), parent constraint should be omitted as there is nothing to specify
- return null;
- }
final String constraintKey = keyBuilder.build(buildContext, constraintDescriptor, classifierSupplier);
+ final SIMPLE_TYPE constraintValue = buildConstraintValue(buildContext, constraintDescriptor, valueTypeSupplier);
return buildFieldFromConstraintDescriptor(constraintDescriptor, constraintKey, constraintValue);
}
@@ -775,7 +764,7 @@ protected abstract OBJECT_FIELD buildFieldFromConstraintDescriptor(@Nonnull Cons
* @param valueTypeSupplier supplies concrete value type for constraint values if value parameter has generic type
* @return input type representing the field value
*/
- @Nullable
+ @Nonnull
protected SIMPLE_TYPE buildConstraintValue(@Nonnull ConstraintBuildContext buildContext,
@Nonnull ConstraintDescriptor constraintDescriptor,
@Nullable ValueTypeSupplier valueTypeSupplier) {
@@ -892,7 +881,7 @@ protected Optional resolveChildDataLocator(@Nonnull ConstraintBuild
*
* If returns null, parent constraint should be omitted, because there are no valid parameters to specify.
*/
- @Nullable
+ @Nonnull
protected SIMPLE_TYPE obtainWrapperObjectConstraintValue(@Nonnull ConstraintBuildContext buildContext,
@Nonnull List valueParameters,
@Nonnull List childParameters,
@@ -941,7 +930,7 @@ protected SIMPLE_TYPE obtainWrapperObjectConstraintValue(@Nonnull ConstraintBuil
*
* Note: this method should not be used directly, instead use {@link #obtainWrapperObjectConstraintValue(ConstraintBuildContext, List, List, List, ValueTypeSupplier)}.
*/
- @Nullable
+ @Nonnull
protected abstract SIMPLE_TYPE buildWrapperObjectConstraintValue(@Nonnull ConstraintBuildContext buildContext,
@Nonnull WrapperObjectKey wrapperObjectKey,
@Nonnull List valueParameters,
diff --git a/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/api/catalog/dataApi/resolver/constraint/ConstraintResolver.java b/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/api/catalog/dataApi/resolver/constraint/ConstraintResolver.java
index 765bdf99b7..16e8253cb7 100644
--- a/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/api/catalog/dataApi/resolver/constraint/ConstraintResolver.java
+++ b/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/api/catalog/dataApi/resolver/constraint/ConstraintResolver.java
@@ -6,7 +6,7 @@
* | __/\ V /| | || (_| | |_| | |_) |
* \___| \_/ |_|\__\__,_|____/|____/
*
- * Copyright (c) 2023-2024
+ * Copyright (c) 2023-2025
*
* Licensed under the Business Source License, Version 1.1 (the "License");
* you may not use this file except in compliance with the License.
@@ -379,7 +379,7 @@ private Object extractValueParameterFromValue(@Nonnull ParsedConstraintDescripto
/**
* Should extract raw argument from wrapper object `value`.
*/
- @Nonnull
+ @Nullable
private Object extractValueArgumentFromWrapperObject(@Nonnull ParsedConstraintDescriptor parsedConstraintDescriptor,
@Nonnull Object value,
@Nonnull ValueParameterDescriptor parameterDescriptor) {
@@ -402,17 +402,22 @@ private Object extractToArgumentFromWrapperRange(@Nonnull ParsedConstraintDescri
return extractRangeFromWrapperRange(parsedConstraintDescriptor, value).get(1);
}
- @Nonnull
+ @Nullable
private Object extractArgumentFromWrapperObject(@Nonnull ParsedConstraintDescriptor parsedConstraintDescriptor,
@Nonnull Object value,
@Nonnull String parameterName) {
+ if (value instanceof Boolean) {
+ // wrapper object is empty (no parameters)
+ return null;
+ }
+
final Map wrapperObject;
try {
//noinspection unchecked
wrapperObject = (Map) value;
} catch (ClassCastException e) {
throw createQueryResolvingInternalError(
- "Constraint `" + parsedConstraintDescriptor + "` expected to be wrapper object but found `" + value + "`."
+ "Constraint `" + parsedConstraintDescriptor.originalKey() + "` expected to be wrapper object but found `" + value + "`."
);
}
return wrapperObject.get(parameterName);
diff --git a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/builder/constraint/GraphQLConstraintSchemaBuilder.java b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/builder/constraint/GraphQLConstraintSchemaBuilder.java
index 2b95d402ec..06c17b6039 100644
--- a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/builder/constraint/GraphQLConstraintSchemaBuilder.java
+++ b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/builder/constraint/GraphQLConstraintSchemaBuilder.java
@@ -254,7 +254,7 @@ protected Map buildChildConstraintValue(@Nonnull Const
return childTypes;
}
- @Nullable
+ @Nonnull
@Override
protected GraphQLInputType buildWrapperObjectConstraintValue(@Nonnull ConstraintBuildContext buildContext,
@Nonnull WrapperObjectKey wrapperObjectKey,
@@ -332,8 +332,11 @@ protected GraphQLInputType buildWrapperObjectConstraintValue(@Nonnull Constraint
});
if (wrapperObjectFields.isEmpty()) {
- // no fields, parent constraint should be omitted
- return null;
+ // no fields (no usable parameters), there is nothing specific to specify, but we still want to be able to use
+ // the constraint without parameters
+ final GraphQLInputType emptyWrapperObject = buildNoneConstraintValue();
+ sharedContext.cacheWrapperObject(wrapperObjectKey, emptyWrapperObject);
+ return emptyWrapperObject;
}
final String wrapperObjectName = constructWrapperObjectName(wrapperObjectKey);
diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/builder/constraint/OpenApiConstraintSchemaBuilder.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/builder/constraint/OpenApiConstraintSchemaBuilder.java
index aee38a09b9..c4c9b4fb80 100644
--- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/builder/constraint/OpenApiConstraintSchemaBuilder.java
+++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/builder/constraint/OpenApiConstraintSchemaBuilder.java
@@ -257,7 +257,7 @@ protected Map buildChildConstraintValue(@Nonnull Cons
}
- @Nullable
+ @Nonnull
@Override
protected OpenApiSimpleType buildWrapperObjectConstraintValue(@Nonnull ConstraintBuildContext buildContext,
@Nonnull WrapperObjectKey wrapperObjectKey,
@@ -335,8 +335,11 @@ protected OpenApiSimpleType buildWrapperObjectConstraintValue(@Nonnull Constrain
});
if (wrapperObjectFields.isEmpty()) {
- // no fields, parent constraint should be omitted
- return null;
+ // no fields (no usable parameters), there is nothing specific to specify, but we still want to be able to use
+ // the constraint without parameters
+ final OpenApiSimpleType emptyWrapperObject = buildNoneConstraintValue();
+ sharedContext.cacheWrapperObject(wrapperObjectKey, emptyWrapperObject);
+ return emptyWrapperObject;
}
final String wrapperObjectName = constructWrapperObjectName(wrapperObjectKey);
diff --git a/evita_test_support/src/main/java/io/evitadb/test/client/query/ConstraintToJsonConverter.java b/evita_test_support/src/main/java/io/evitadb/test/client/query/ConstraintToJsonConverter.java
index 6466e40097..7896956d1e 100644
--- a/evita_test_support/src/main/java/io/evitadb/test/client/query/ConstraintToJsonConverter.java
+++ b/evita_test_support/src/main/java/io/evitadb/test/client/query/ConstraintToJsonConverter.java
@@ -6,7 +6,7 @@
* | __/\ V /| | || (_| | |_| | |_) |
* \___| \_/ |_|\__\__,_|____/|____/
*
- * Copyright (c) 2023
+ * Copyright (c) 2023-2025
*
* Licensed under the Business Source License, Version 1.1 (the "License");
* you may not use this file except in compliance with the License.
@@ -409,6 +409,11 @@ private JsonNode convertWrapperObjectStructure(@Nonnull ConstraintToJsonConvertC
));
});
+ if (wrapperObject.isEmpty()) {
+ // there are no usable parameters, but the constraint is still valid
+ return convertNoneStructure();
+ }
+
return wrapperObject;
}
From b2a4f7cda93a85b3e31ce04de17950a79941e5a7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Novotn=C3=BD?=
Date: Thu, 6 Feb 2025 16:37:37 +0100
Subject: [PATCH 10/22] fix: updating catalog name on catalog rename
---
.../main/java/io/evitadb/core/Catalog.java | 2 +
.../core/traffic/TrafficRecordingEngine.java | 44 ++++++++++++++++---
.../DefaultCatalogPersistenceServiceTest.java | 18 ++++++--
.../evitadb/store/traffic/DiskRingBuffer.java | 5 ++-
4 files changed, 57 insertions(+), 12 deletions(-)
diff --git a/evita_engine/src/main/java/io/evitadb/core/Catalog.java b/evita_engine/src/main/java/io/evitadb/core/Catalog.java
index 6878219f56..fe2c5608b5 100644
--- a/evita_engine/src/main/java/io/evitadb/core/Catalog.java
+++ b/evita_engine/src/main/java/io/evitadb/core/Catalog.java
@@ -558,6 +558,8 @@ public Catalog(
previousCatalogVersion.getInternalSchema(),
this.entitySchemaAccessor
);
+
+ this.trafficRecordingEngine.updateCatalogName(catalogSchema.getName());
this.schema = new TransactionalReference<>(new CatalogSchemaDecorator(catalogSchema));
this.dataStoreBuffer = catalogState == CatalogState.WARMING_UP ?
new WarmUpDataStoreMemoryBuffer(storagePartPersistenceService) :
diff --git a/evita_engine/src/main/java/io/evitadb/core/traffic/TrafficRecordingEngine.java b/evita_engine/src/main/java/io/evitadb/core/traffic/TrafficRecordingEngine.java
index e9e74b3f66..630bc7ee0e 100644
--- a/evita_engine/src/main/java/io/evitadb/core/traffic/TrafficRecordingEngine.java
+++ b/evita_engine/src/main/java/io/evitadb/core/traffic/TrafficRecordingEngine.java
@@ -85,12 +85,11 @@
* @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2024
*/
@Slf4j
-@RequiredArgsConstructor
public class TrafficRecordingEngine implements TrafficRecordingReader {
public static final String LABEL_TRACE_ID = "trace-id";
public static final String LABEL_CLIENT_ID = "client-id";
public static final String LABEL_IP_ADDRESS = "ip-address";
- private final String catalogName;
+ private final AtomicReference catalogName;
private final StorageOptions storageOptions;
@Getter private final TrafficRecordingOptions trafficOptions;
private final ExportFileService exportFileService;
@@ -151,20 +150,51 @@ public TrafficRecordingEngine(
@Nonnull ExportFileService exportFileService,
@Nonnull Scheduler scheduler
) {
- this.catalogName = catalogName;
+ this.catalogName = new AtomicReference<>(catalogName);
this.storageOptions = configuration.storage();
this.trafficOptions = configuration.server().trafficRecording();
this.exportFileService = exportFileService;
this.scheduler = scheduler;
- if (configuration.server().trafficRecording().enabled()) {
+ this.tracingContext = tracingContext;
+ initializeTrafficRecorder(catalogName);
+ }
+
+ /**
+ * Initializes the traffic recorder for the specified catalog. The traffic recorder can be enabled or disabled
+ * based on the configuration provided. When enabled, a specific traffic recorder instance is initialized;
+ * otherwise, a no-operation (NoOp) traffic recorder is set.
+ *
+ * @param catalogName The name of the catalog for which the traffic recorder should be initialized.
+ * This parameter must not be null.
+ */
+ private void initializeTrafficRecorder(@Nonnull String catalogName) {
+ final TrafficRecorder existingTrafficRecorder = this.trafficRecorder.get();
+ if (existingTrafficRecorder != null) {
+ IOUtils.closeQuietly(existingTrafficRecorder::close);
+ }
+ if (this.trafficOptions.enabled()) {
final TrafficRecorder trafficRecorderInstance = getRichTrafficRecorderIfPossible(
- this.catalogName, this.exportFileService, this.scheduler, this.storageOptions, this.trafficOptions
+ catalogName, this.exportFileService, this.scheduler, this.storageOptions, this.trafficOptions
);
this.trafficRecorder.set(trafficRecorderInstance);
} else {
this.trafficRecorder.set(NoOpTrafficRecorder.INSTANCE);
}
- this.tracingContext = tracingContext;
+ }
+
+ /**
+ * Updates the catalog name and initializes the traffic recorder with the new catalog name
+ * if the provided name differs from the current one.
+ *
+ * @param catalogName the new name of the catalog. This value must not be null.
+ */
+ public void updateCatalogName(@Nonnull String catalogName) {
+ this.catalogName.getAndUpdate(previous -> {
+ if (!Objects.equals(previous, catalogName)) {
+ initializeTrafficRecorder(catalogName);
+ }
+ return catalogName;
+ });
}
/**
@@ -181,7 +211,7 @@ public void startRecording(int samplingRate, @Nullable SessionSink sessionSink)
final TrafficRecorder defaultTrafficRecorder = this.trafficRecorder.get();
if (defaultTrafficRecorder instanceof NoOpTrafficRecorder) {
final TrafficRecorder richTrafficRecorderInstance = getRichTrafficRecorderIfPossible(
- this.catalogName, this.exportFileService, this.scheduler, this.storageOptions, this.trafficOptions
+ this.catalogName.get(), this.exportFileService, this.scheduler, this.storageOptions, this.trafficOptions
);
this.suppressedTrafficRecorder.set(defaultTrafficRecorder);
this.trafficRecorder.set(richTrafficRecorderInstance);
diff --git a/evita_functional_tests/src/test/java/io/evitadb/store/catalog/DefaultCatalogPersistenceServiceTest.java b/evita_functional_tests/src/test/java/io/evitadb/store/catalog/DefaultCatalogPersistenceServiceTest.java
index 66146c2954..788f29eee7 100644
--- a/evita_functional_tests/src/test/java/io/evitadb/store/catalog/DefaultCatalogPersistenceServiceTest.java
+++ b/evita_functional_tests/src/test/java/io/evitadb/store/catalog/DefaultCatalogPersistenceServiceTest.java
@@ -27,6 +27,8 @@
import com.esotericsoftware.kryo.util.Pool;
import io.evitadb.api.CatalogContract;
import io.evitadb.api.CatalogState;
+import io.evitadb.api.configuration.EvitaConfiguration;
+import io.evitadb.api.configuration.ServerOptions;
import io.evitadb.api.configuration.StorageOptions;
import io.evitadb.api.configuration.ThreadPoolOptions;
import io.evitadb.api.configuration.TrafficRecordingOptions;
@@ -205,11 +207,19 @@ private static void trimAndCheck(
private static TrafficRecordingEngine createTrafficRecordingEngine(@Nonnull SealedCatalogSchema catalogSchema) {
return new TrafficRecordingEngine(
catalogSchema.getName(),
- StorageOptions.builder().build(),
- TrafficRecordingOptions.builder().build(),
+ DefaultTracingContext.INSTANCE,
+ EvitaConfiguration.builder()
+ .storage(StorageOptions.builder().build())
+ .server(
+ ServerOptions.builder()
+ .trafficRecording(
+ TrafficRecordingOptions.builder()
+ .build()
+ ).build()
+ )
+ .build(),
Mockito.mock(ExportFileService.class),
- Mockito.mock(Scheduler.class),
- DefaultTracingContext.INSTANCE
+ Mockito.mock(Scheduler.class)
);
}
diff --git a/evita_store/evita_store_server/src/main/java/io/evitadb/store/traffic/DiskRingBuffer.java b/evita_store/evita_store_server/src/main/java/io/evitadb/store/traffic/DiskRingBuffer.java
index a51028f581..c7bd61e0d1 100644
--- a/evita_store/evita_store_server/src/main/java/io/evitadb/store/traffic/DiskRingBuffer.java
+++ b/evita_store/evita_store_server/src/main/java/io/evitadb/store/traffic/DiskRingBuffer.java
@@ -544,7 +544,10 @@ public void indexData(
diskBufferFileReadInputStream,
reader,
e -> {
- throw new IndexNotReady(0);
+ // session would be invalid, remove it from the index
+ index.removeSession(sessionLocation.sequenceOrder());
+ log.error("Error while reading session records: {}", e.getMessage());
+ return null;
}
)
.forEach(tr -> index.indexRecording(sessionLocation.sequenceOrder(), tr));
From 086e000ad3f8820166a2b3805a2a5c29f2efc46d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Novotn=C3=BD?=
Date: Fri, 7 Feb 2025 12:58:03 +0100
Subject: [PATCH 11/22] fix: traffic recording is not performed in warm up
stage
---
.../main/java/io/evitadb/core/Catalog.java | 4 +-
.../core/traffic/TrafficRecordingEngine.java | 42 ++++++++++-----
.../traffic/OffHeapTrafficRecorderTest.java | 51 ++++++++++++++++++-
3 files changed, 82 insertions(+), 15 deletions(-)
diff --git a/evita_engine/src/main/java/io/evitadb/core/Catalog.java b/evita_engine/src/main/java/io/evitadb/core/Catalog.java
index fe2c5608b5..33248de135 100644
--- a/evita_engine/src/main/java/io/evitadb/core/Catalog.java
+++ b/evita_engine/src/main/java/io/evitadb/core/Catalog.java
@@ -361,6 +361,7 @@ public Catalog(
);
this.trafficRecordingEngine = new TrafficRecordingEngine(
internalCatalogSchema.getName(),
+ this.state,
tracingContext,
evitaConfiguration,
exportFileService,
@@ -426,6 +427,7 @@ public Catalog(
this.cacheSupervisor = cacheSupervisor;
this.trafficRecordingEngine = new TrafficRecordingEngine(
catalogSchema.getName(),
+ this.state,
tracingContext,
evitaConfiguration,
exportFileService,
@@ -559,7 +561,7 @@ public Catalog(
this.entitySchemaAccessor
);
- this.trafficRecordingEngine.updateCatalogName(catalogSchema.getName());
+ this.trafficRecordingEngine.updateCatalogName(catalogSchema.getName(), this.state);
this.schema = new TransactionalReference<>(new CatalogSchemaDecorator(catalogSchema));
this.dataStoreBuffer = catalogState == CatalogState.WARMING_UP ?
new WarmUpDataStoreMemoryBuffer(storagePartPersistenceService) :
diff --git a/evita_engine/src/main/java/io/evitadb/core/traffic/TrafficRecordingEngine.java b/evita_engine/src/main/java/io/evitadb/core/traffic/TrafficRecordingEngine.java
index 630bc7ee0e..59bf02e171 100644
--- a/evita_engine/src/main/java/io/evitadb/core/traffic/TrafficRecordingEngine.java
+++ b/evita_engine/src/main/java/io/evitadb/core/traffic/TrafficRecordingEngine.java
@@ -24,6 +24,7 @@
package io.evitadb.core.traffic;
+import io.evitadb.api.CatalogState;
import io.evitadb.api.LabelIntrospector;
import io.evitadb.api.TrafficRecordingReader;
import io.evitadb.api.configuration.EvitaConfiguration;
@@ -89,7 +90,7 @@ public class TrafficRecordingEngine implements TrafficRecordingReader {
public static final String LABEL_TRACE_ID = "trace-id";
public static final String LABEL_CLIENT_ID = "client-id";
public static final String LABEL_IP_ADDRESS = "ip-address";
- private final AtomicReference catalogName;
+ private final AtomicReference catalogInfo;
private final StorageOptions storageOptions;
@Getter private final TrafficRecordingOptions trafficOptions;
private final ExportFileService exportFileService;
@@ -145,18 +146,20 @@ private static TrafficRecorder getRichTrafficRecorderIfPossible(
public TrafficRecordingEngine(
@Nonnull String catalogName,
+ @Nonnull CatalogState catalogState,
@Nonnull TracingContext tracingContext,
@Nonnull EvitaConfiguration configuration,
@Nonnull ExportFileService exportFileService,
@Nonnull Scheduler scheduler
) {
- this.catalogName = new AtomicReference<>(catalogName);
+ final CatalogInfo catalogInfo = new CatalogInfo(catalogName, catalogState);
+ this.catalogInfo = new AtomicReference<>(catalogInfo);
this.storageOptions = configuration.storage();
this.trafficOptions = configuration.server().trafficRecording();
this.exportFileService = exportFileService;
this.scheduler = scheduler;
this.tracingContext = tracingContext;
- initializeTrafficRecorder(catalogName);
+ initializeTrafficRecorder(catalogInfo);
}
/**
@@ -164,17 +167,18 @@ public TrafficRecordingEngine(
* based on the configuration provided. When enabled, a specific traffic recorder instance is initialized;
* otherwise, a no-operation (NoOp) traffic recorder is set.
*
- * @param catalogName The name of the catalog for which the traffic recorder should be initialized.
+ * @param catalogInfo The name and state of the catalog for which the traffic recorder should be initialized.
* This parameter must not be null.
*/
- private void initializeTrafficRecorder(@Nonnull String catalogName) {
+ private void initializeTrafficRecorder(@Nonnull CatalogInfo catalogInfo) {
final TrafficRecorder existingTrafficRecorder = this.trafficRecorder.get();
if (existingTrafficRecorder != null) {
IOUtils.closeQuietly(existingTrafficRecorder::close);
}
- if (this.trafficOptions.enabled()) {
+ if (this.trafficOptions.enabled() && catalogInfo.state() == CatalogState.ALIVE) {
final TrafficRecorder trafficRecorderInstance = getRichTrafficRecorderIfPossible(
- catalogName, this.exportFileService, this.scheduler, this.storageOptions, this.trafficOptions
+ catalogInfo.catalogName(),
+ this.exportFileService, this.scheduler, this.storageOptions, this.trafficOptions
);
this.trafficRecorder.set(trafficRecorderInstance);
} else {
@@ -188,12 +192,13 @@ private void initializeTrafficRecorder(@Nonnull String catalogName) {
*
* @param catalogName the new name of the catalog. This value must not be null.
*/
- public void updateCatalogName(@Nonnull String catalogName) {
- this.catalogName.getAndUpdate(previous -> {
- if (!Objects.equals(previous, catalogName)) {
- initializeTrafficRecorder(catalogName);
+ public void updateCatalogName(@Nonnull String catalogName, @Nonnull CatalogState state) {
+ this.catalogInfo.getAndUpdate(previous -> {
+ final CatalogInfo newCatalogInfo = new CatalogInfo(catalogName, state);
+ if (!Objects.equals(previous, newCatalogInfo)) {
+ initializeTrafficRecorder(newCatalogInfo);
}
- return catalogName;
+ return newCatalogInfo;
});
}
@@ -211,7 +216,8 @@ public void startRecording(int samplingRate, @Nullable SessionSink sessionSink)
final TrafficRecorder defaultTrafficRecorder = this.trafficRecorder.get();
if (defaultTrafficRecorder instanceof NoOpTrafficRecorder) {
final TrafficRecorder richTrafficRecorderInstance = getRichTrafficRecorderIfPossible(
- this.catalogName.get(), this.exportFileService, this.scheduler, this.storageOptions, this.trafficOptions
+ this.catalogInfo.get().catalogName(),
+ this.exportFileService, this.scheduler, this.storageOptions, this.trafficOptions
);
this.suppressedTrafficRecorder.set(defaultTrafficRecorder);
this.trafficRecorder.set(richTrafficRecorderInstance);
@@ -689,4 +695,14 @@ public void finishWithException(@Nonnull Exception ex) {
}
+ /**
+ * Represents a catalog name and its state.
+ * @param catalogName the name of the catalog
+ * @param state the state of the catalog
+ */
+ private record CatalogInfo(
+ @Nonnull String catalogName,
+ @Nonnull CatalogState state
+ ) {}
+
}
diff --git a/evita_functional_tests/src/test/java/io/evitadb/store/traffic/OffHeapTrafficRecorderTest.java b/evita_functional_tests/src/test/java/io/evitadb/store/traffic/OffHeapTrafficRecorderTest.java
index d254d3d2ee..77c0657d93 100644
--- a/evita_functional_tests/src/test/java/io/evitadb/store/traffic/OffHeapTrafficRecorderTest.java
+++ b/evita_functional_tests/src/test/java/io/evitadb/store/traffic/OffHeapTrafficRecorderTest.java
@@ -32,6 +32,7 @@
import io.evitadb.api.query.order.OrderDirection;
import io.evitadb.api.requestResponse.data.mutation.EntityMutation.EntityExistence;
import io.evitadb.api.requestResponse.data.mutation.EntityUpsertMutation;
+import io.evitadb.api.requestResponse.data.mutation.associatedData.UpsertAssociatedDataMutation;
import io.evitadb.api.requestResponse.data.mutation.attribute.UpsertAttributeMutation;
import io.evitadb.api.requestResponse.trafficRecording.*;
import io.evitadb.api.requestResponse.trafficRecording.TrafficRecordingCaptureRequest.TrafficRecordingType;
@@ -94,7 +95,7 @@ void setUp() {
this.trafficRecorder = new OffHeapTrafficRecorder(2_048);
this.exportDirectory.toFile().mkdirs();
final StorageOptions storageOptions = StorageOptions.builder()
- .outputBufferSize(1_024)
+ .outputBufferSize(2_048)
.exportDirectory(this.exportDirectory)
.build();
final Scheduler scheduler = new Scheduler(new ImmediateExecutorService(1));
@@ -574,6 +575,54 @@ void shouldRecordMoreDataThanBufferSizeForcingWrapAround() {
}
}
+ @Test
+ void shouldRecordAsLargeAsTheBufferItself() {
+ final UUID sessionId = UUIDUtil.randomUUID();
+ String[] veryLongString = new String[1024];
+ for (int i = 0; i < 1024; i++) {
+ StringBuilder singleString = new StringBuilder();
+ for (int j = 32; j < 61; j++) {
+ singleString.append(Character.valueOf((char)(32 + j % (126 - 32))));
+ }
+ veryLongString[i] = singleString.toString();
+ }
+ this.trafficRecorder.createSession(sessionId, 1, OffsetDateTime.now());
+ this.trafficRecorder.recordMutation(
+ sessionId,
+ OffsetDateTime.now(),
+ new EntityUpsertMutation(
+ Entities.PRODUCT,
+ 1,
+ EntityExistence.MUST_NOT_EXIST,
+ new UpsertAssociatedDataMutation("a", veryLongString)
+ ),
+ null
+ );
+ this.trafficRecorder.closeSession(sessionId, null);
+
+ // wait for the data to be written to the disk
+ waitUntilDataBecomeAvailable(sessionId, 10_000);
+
+ try (
+ final Stream recordings = this.trafficRecorder.getRecordings(
+ TrafficRecordingCaptureRequest.builder()
+ .content(TrafficRecordingContent.BODY)
+ .sinceSessionSequenceId(0L)
+ .sinceRecordSessionOffset(0)
+ .build()
+ )
+ ) {
+ final List allRecordings = recordings.toList();
+
+ assertEquals(3, allRecordings.size(), "Actual size: " + allRecordings.size());
+ final MutationContainer mutation = (MutationContainer) allRecordings.get(1);
+ final EntityUpsertMutation upsertMutation = (EntityUpsertMutation) mutation.mutation();
+ final UpsertAssociatedDataMutation upsertAssociatedDataMutation = (UpsertAssociatedDataMutation) upsertMutation.getLocalMutations().get(0);
+ final String[] value = (String[]) upsertAssociatedDataMutation.getAssociatedDataValue();
+ assertEquals(1024, value.length);
+ }
+ }
+
@Disabled("This test fails to often in parallel suite. Needs to be executed manually as a standalone test.")
@Test
void shouldRecordDataInParallelAndQueryOnThem() throws InterruptedException {
From 13ef1a7fbf4e68bd7692e34d19f7881159b80adb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Novotn=C3=BD?=
Date: Fri, 7 Feb 2025 13:03:09 +0100
Subject: [PATCH 12/22] fix: traffic recording is not performed in warm up
stage
---
.../store/catalog/DefaultCatalogPersistenceServiceTest.java | 1 +
.../io/evitadb/store/traffic/OffHeapTrafficRecorder.java | 4 ++--
.../java/io/evitadb/store/traffic/TrafficRecordingIndex.java | 5 ++++-
3 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/evita_functional_tests/src/test/java/io/evitadb/store/catalog/DefaultCatalogPersistenceServiceTest.java b/evita_functional_tests/src/test/java/io/evitadb/store/catalog/DefaultCatalogPersistenceServiceTest.java
index 788f29eee7..b9df297d3b 100644
--- a/evita_functional_tests/src/test/java/io/evitadb/store/catalog/DefaultCatalogPersistenceServiceTest.java
+++ b/evita_functional_tests/src/test/java/io/evitadb/store/catalog/DefaultCatalogPersistenceServiceTest.java
@@ -207,6 +207,7 @@ private static void trimAndCheck(
private static TrafficRecordingEngine createTrafficRecordingEngine(@Nonnull SealedCatalogSchema catalogSchema) {
return new TrafficRecordingEngine(
catalogSchema.getName(),
+ CatalogState.WARMING_UP,
DefaultTracingContext.INSTANCE,
EvitaConfiguration.builder()
.storage(StorageOptions.builder().build())
diff --git a/evita_store/evita_store_server/src/main/java/io/evitadb/store/traffic/OffHeapTrafficRecorder.java b/evita_store/evita_store_server/src/main/java/io/evitadb/store/traffic/OffHeapTrafficRecorder.java
index b2cb3f9dd9..18b0eed020 100644
--- a/evita_store/evita_store_server/src/main/java/io/evitadb/store/traffic/OffHeapTrafficRecorder.java
+++ b/evita_store/evita_store_server/src/main/java/io/evitadb/store/traffic/OffHeapTrafficRecorder.java
@@ -717,8 +717,8 @@ private NumberedByteBuffer prepareStorageBlock() {
private long index() {
try {
this.diskBuffer.indexData(this::readTrafficRecord);
- } catch (IndexNotReady ex) {
- this.indexTask.scheduleImmediately();
+ } catch (Exception ex) {
+ log.error("Failed to index disk buffer.", ex);
}
return -1L;
}
diff --git a/evita_store/evita_store_server/src/main/java/io/evitadb/store/traffic/TrafficRecordingIndex.java b/evita_store/evita_store_server/src/main/java/io/evitadb/store/traffic/TrafficRecordingIndex.java
index ed87dedfa8..8ce1fae124 100644
--- a/evita_store/evita_store_server/src/main/java/io/evitadb/store/traffic/TrafficRecordingIndex.java
+++ b/evita_store/evita_store_server/src/main/java/io/evitadb/store/traffic/TrafficRecordingIndex.java
@@ -675,7 +675,10 @@ private void removeSessionFromIndexes(long sessionSequenceOrder) {
this.sessionFetchCountIndex.delete(sessionDescriptor.getMaxFetchCount());
this.sessionBytesFetchedIndex.delete(sessionDescriptor.getMaxBytesFetchedTotal());
for (TrafficRecordingType recordingType : sessionDescriptor.getRecordingTypes()) {
- this.sessionRecordingTypeIndex.get(recordingType).delete(sessionSequenceOrder);
+ final TransactionalObjectBPlusTree typeIndex = this.sessionRecordingTypeIndex.get(recordingType);
+ if (typeIndex != null) {
+ typeIndex.delete(sessionSequenceOrder);
+ }
}
}
From 2ac5985cdab6d8880b9666d3d89118ddaa7369bd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Novotn=C3=BD?=
Date: Fri, 7 Feb 2025 13:10:49 +0100
Subject: [PATCH 13/22] fix: filterBy serializes only first child
---
.../store/query/QuerySerializationTest.java | 10 +++++++++-
.../serializer/filter/FilterBySerializer.java | 14 +++++++++++---
2 files changed, 20 insertions(+), 4 deletions(-)
diff --git a/evita_functional_tests/src/test/java/io/evitadb/store/query/QuerySerializationTest.java b/evita_functional_tests/src/test/java/io/evitadb/store/query/QuerySerializationTest.java
index b6bf56ff50..4a480dfd25 100644
--- a/evita_functional_tests/src/test/java/io/evitadb/store/query/QuerySerializationTest.java
+++ b/evita_functional_tests/src/test/java/io/evitadb/store/query/QuerySerializationTest.java
@@ -6,7 +6,7 @@
* | __/\ V /| | || (_| | |_| | |_) |
* \___| \_/ |_|\__\__,_|____/|____/
*
- * Copyright (c) 2024
+ * Copyright (c) 2024-2025
*
* Licensed under the Business Source License, Version 1.1 (the "License");
* you may not use this file except in compliance with the License.
@@ -68,9 +68,17 @@ void shouldSerializeQuery() {
assertSerializationRound(Query.query(collection("a"), require(debug(DebugMode.VERIFY_ALTERNATIVE_INDEX_RESULTS), entityFetchAll())));
}
+ @Test
+ void shouldSerializeHeadConstraints() {
+ assertSerializationRound(collection("a"));
+ assertSerializationRound(label("a", "b"));
+ assertSerializationRound(head(collection("a"), label("a", "b"), label("c", "d")));
+ }
+
@Test
void shouldSerializeFilteringConstraints() {
assertSerializationRound(filterBy(attributeEquals("a", "b")));
+ assertSerializationRound(filterBy(attributeEquals("a", "b"), attributeIs("d", AttributeSpecialValue.NULL)));
assertSerializationRound(and(attributeEquals("a", "b"), attributeEquals("c", "d")));
assertSerializationRound(or(attributeEquals("a", "b"), attributeEquals("c", "d")));
assertSerializationRound(not(attributeEquals("a", "b")));
diff --git a/evita_store/evita_store_server/src/main/java/io/evitadb/store/query/serializer/filter/FilterBySerializer.java b/evita_store/evita_store_server/src/main/java/io/evitadb/store/query/serializer/filter/FilterBySerializer.java
index 7e21e70939..2b4d8b3e83 100644
--- a/evita_store/evita_store_server/src/main/java/io/evitadb/store/query/serializer/filter/FilterBySerializer.java
+++ b/evita_store/evita_store_server/src/main/java/io/evitadb/store/query/serializer/filter/FilterBySerializer.java
@@ -6,7 +6,7 @@
* | __/\ V /| | || (_| | |_| | |_) |
* \___| \_/ |_|\__\__,_|____/|____/
*
- * Copyright (c) 2023
+ * Copyright (c) 2023-2025
*
* Licensed under the Business Source License, Version 1.1 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,12 +41,20 @@ public class FilterBySerializer extends Serializer {
@Override
public void write(Kryo kryo, Output output, FilterBy object) {
- kryo.writeClassAndObject(output, object.getChildren()[0]);
+ final FilterConstraint[] children = object.getChildren();
+ output.writeVarInt(children.length, true);
+ for (FilterConstraint child : children) {
+ kryo.writeClassAndObject(output, child);
+ }
}
@Override
public FilterBy read(Kryo kryo, Input input, Class extends FilterBy> type) {
- return new FilterBy((FilterConstraint) kryo.readClassAndObject(input));
+ final FilterConstraint[] children = new FilterConstraint[input.readVarInt(true)];
+ for (int i = 0; i < children.length; i++) {
+ children[i] = (FilterConstraint) kryo.readClassAndObject(input);
+ }
+ return new FilterBy(children);
}
}
From 70b4973340888a5147af2b020db81c1d1d1898b0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Novotn=C3=BD?=
Date: Fri, 7 Feb 2025 14:45:11 +0100
Subject: [PATCH 14/22] doc: documented traffic recorder
---
.../get-started/example/server-startup.java | 25 ++++++-
documentation/user/en/operate/observe.md | 30 ++++++++
documentation/user/en/query/basics.md | 8 +--
.../en/query/examples/complexGrammar.java | 69 ++++++++++++++-----
.../user/en/query/examples/grammar.java | 48 +++++++++++--
.../en/query/examples/randomOrderQuery.java | 44 ++++++++++--
.../user/en/query/examples/simplestQuery.java | 36 +++++++++-
.../en/query/header/examples/labels.evitaql | 10 +++
.../en/query/header/examples/labels.graphql | 17 +++++
.../user/en/query/header/examples/labels.java | 40 +++++++++++
.../user/en/query/header/examples/labels.rest | 11 +++
documentation/user/en/query/header/header.md | 13 +++-
.../JavaPrettyPrintingVisitor.java | 4 +-
.../documentation/UserDocumentationTest.java | 10 +--
.../parser/visitor/EvitaQLQueryVisitor.java | 2 +-
15 files changed, 319 insertions(+), 48 deletions(-)
create mode 100644 documentation/user/en/query/header/examples/labels.evitaql
create mode 100644 documentation/user/en/query/header/examples/labels.graphql
create mode 100644 documentation/user/en/query/header/examples/labels.java
create mode 100644 documentation/user/en/query/header/examples/labels.rest
diff --git a/documentation/user/en/get-started/example/server-startup.java b/documentation/user/en/get-started/example/server-startup.java
index 2f5e53c4ec..455b63c543 100644
--- a/documentation/user/en/get-started/example/server-startup.java
+++ b/documentation/user/en/get-started/example/server-startup.java
@@ -1,3 +1,26 @@
+/*
+ *
+ * _ _ ____ ____
+ * _____ _(_) |_ __ _| _ \| __ )
+ * / _ \ \ / / | __/ _` | | | | _ \
+ * | __/\ V /| | || (_| | |_| | |_) |
+ * \___| \_/ |_|\__\__,_|____/|____/
+ *
+ * Copyright (c) 2025
+ *
+ * Licensed under the Business Source License, Version 1.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://github.com/FgForrest/evitaDB/blob/master/LICENSE
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
final Evita evita = new Evita(
EvitaConfiguration.builder()
.server(
@@ -9,7 +32,7 @@
.storage(
// configure additional storage options, or let the defaults apply
StorageOptions.builder()
- .storageDirectory(Path.of("/data"))
+ .storageDirectory(Path.of("./data"))
.build()
)
.cache(
diff --git a/documentation/user/en/operate/observe.md b/documentation/user/en/operate/observe.md
index 879beca228..95dcc4a008 100644
--- a/documentation/user/en/operate/observe.md
+++ b/documentation/user/en/operate/observe.md
@@ -528,6 +528,36 @@ in [Logback](https://logback.qos.ch/index.html) this can be done using `%X{trace
```
+## Traffic recording
+
+In addition to the observability tools mentioned above, evitaDB also offers the ability to record all incoming traffic
+to the server. This feature is useful for debugging and development purposes, as it allows you to play back the recorded
+traffic and analyse the behaviour of the server in detail. The traffic recording feature is disabled by default and must
+be enabled in the server [configuration](../operate/configure.md#traffic-recording-configuration).
+
+These settings are recommended for local development:
+
+```yaml
+ trafficRecording:
+ enabled: true
+ sourceQueryTracking: true
+ trafficFlushIntervalInMilliseconds: 0
+```
+
+For test/staging environments, omit `trafficFlushIntervalInMilliseconds` and leave it at the default. If you enable
+traffic logging in production, disable `sourceQueryTracking` as you won't normally need to access the query source code
+in production. In production you'll probably want to set a sampling rate using `trafficSamplingPercentage`.
+
+Besides having access to the `Active Traffic Recording` tab in evitaLab, where you can list through all sessions,
+queries, mutations and entity fetches, you can also issue a traffic reporting task that will save the traffic data in
+a ZIP file and make it available for download. This file can be used for further analysis or to replay the traffic on
+different evitaDB instances.
+
+The recorded traffic can be browsed and filtered in evitaLab and any query can be easily executed in the corresponding
+query console on the current dataset. Records can also be filtered by custom [labels](../query/header/header.md#label),
+traceIds or protocol types. You can easily isolate sets of traffic records that relate to a single business case, such
+as a single page rendering or a single API call.
+
## Reference documentation
[Java Flight Recorder events](/documentation/user/en/operate/reference/jfr-events.md)
diff --git a/documentation/user/en/query/basics.md b/documentation/user/en/query/basics.md
index b5ba272228..0cb9ab8983 100644
--- a/documentation/user/en/query/basics.md
+++ b/documentation/user/en/query/basics.md
@@ -64,14 +64,14 @@ The grammar of a query is as follows:
The grammar of a full query is as follows:
-
+
[Example of grammar of a query](/documentation/user/en/query/examples/grammar.evitaql)
Or more complex one:
-
+
[Example of grammar of a complex query](/documentation/user/en/query/examples/complexGrammar.evitaql)
@@ -134,7 +134,7 @@ I.e. the following query is still a valid query and represents the simplest quer
-
+
[Example of the simplest query](/documentation/user/en/query/examples/simplestQuery.evitaql)
@@ -142,7 +142,7 @@ I.e. the following query is still a valid query and represents the simplest quer
... or even this one (although it is recommended to keep the order for better readability:
`head`, `filterBy`, `orderBy`, `require`):
-
+
[Example random order of query parts](/documentation/user/en/query/examples/randomOrderQuery.evitaql)
diff --git a/documentation/user/en/query/examples/complexGrammar.java b/documentation/user/en/query/examples/complexGrammar.java
index 0525be0cca..9cc52e8632 100644
--- a/documentation/user/en/query/examples/complexGrammar.java
+++ b/documentation/user/en/query/examples/complexGrammar.java
@@ -1,19 +1,50 @@
-query(
- collection("Product"),
- filterBy(
- and(
- entityPrimaryKeyInSet(1, 2, 3),
- attributeEquals("visibility", "VISIBLE")
- )
- ),
- orderBy(
- attributeNatural("code", ASC),
- attributeNatural("priority", DESC)
- ),
- require(
- entityFetch(
- attributeContentAll(), priceContentAll()
- ),
- facetSummary()
- )
-)
\ No newline at end of file
+/*
+ *
+ * _ _ ____ ____
+ * _____ _(_) |_ __ _| _ \| __ )
+ * / _ \ \ / / | __/ _` | | | | _ \
+ * | __/\ V /| | || (_| | |_| | |_) |
+ * \___| \_/ |_|\__\__,_|____/|____/
+ *
+ * Copyright (c) 2025
+ *
+ * Licensed under the Business Source License, Version 1.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://github.com/FgForrest/evitaDB/blob/master/LICENSE
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+final EvitaResponse entities = evita.queryCatalog(
+ "evita",
+ session -> {
+ return session.querySealedEntity(
+ query(
+ collection("Product"),
+ filterBy(
+ and(
+ entityPrimaryKeyInSet(1, 2, 3),
+ attributeEquals("status", "ACTIVE")
+ )
+ ),
+ orderBy(
+ attributeNatural("code", ASC),
+ attributeNatural("catalogNumber", DESC)
+ ),
+ require(
+ entityFetch(
+ attributeContentAll(),
+ priceContentAll()
+ ),
+ facetSummary(COUNTS)
+ )
+ )
+ );
+ }
+);
\ No newline at end of file
diff --git a/documentation/user/en/query/examples/grammar.java b/documentation/user/en/query/examples/grammar.java
index 24651fcf0a..5f893f7dcb 100644
--- a/documentation/user/en/query/examples/grammar.java
+++ b/documentation/user/en/query/examples/grammar.java
@@ -1,6 +1,42 @@
-query(
- collection("Product"),
- filterBy(entityPrimaryKeyInSet(1, 2, 3)),
- orderBy(attributeNatural("code", DESC)),
- require(entityFetch())
-)
\ No newline at end of file
+/*
+ *
+ * _ _ ____ ____
+ * _____ _(_) |_ __ _| _ \| __ )
+ * / _ \ \ / / | __/ _` | | | | _ \
+ * | __/\ V /| | || (_| | |_| | |_) |
+ * \___| \_/ |_|\__\__,_|____/|____/
+ *
+ * Copyright (c) 2025
+ *
+ * Licensed under the Business Source License, Version 1.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://github.com/FgForrest/evitaDB/blob/master/LICENSE
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+final EvitaResponse entities = evita.queryCatalog(
+ "evita",
+ session -> {
+ return session.querySealedEntity(
+ query(
+ collection("Product"),
+ filterBy(
+ entityPrimaryKeyInSet(1, 2, 3)
+ ),
+ orderBy(
+ attributeNatural("code", DESC)
+ ),
+ require(
+ entityFetch()
+ )
+ )
+ );
+ }
+);
\ No newline at end of file
diff --git a/documentation/user/en/query/examples/randomOrderQuery.java b/documentation/user/en/query/examples/randomOrderQuery.java
index a5cd123229..6ef687917f 100644
--- a/documentation/user/en/query/examples/randomOrderQuery.java
+++ b/documentation/user/en/query/examples/randomOrderQuery.java
@@ -1,5 +1,39 @@
-query(
- collection("Product"),
- orderBy(attributeNatural("code", ASC)),
- require(entityFetch())
-)
\ No newline at end of file
+/*
+ *
+ * _ _ ____ ____
+ * _____ _(_) |_ __ _| _ \| __ )
+ * / _ \ \ / / | __/ _` | | | | _ \
+ * | __/\ V /| | || (_| | |_| | |_) |
+ * \___| \_/ |_|\__\__,_|____/|____/
+ *
+ * Copyright (c) 2025
+ *
+ * Licensed under the Business Source License, Version 1.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://github.com/FgForrest/evitaDB/blob/master/LICENSE
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+final EvitaResponse entities = evita.queryCatalog(
+ "evita",
+ session -> {
+ return session.querySealedEntity(
+ query(
+ collection("Product"),
+ orderBy(
+ attributeNatural("code", ASC)
+ ),
+ require(
+ entityFetch()
+ )
+ )
+ );
+ }
+);
\ No newline at end of file
diff --git a/documentation/user/en/query/examples/simplestQuery.java b/documentation/user/en/query/examples/simplestQuery.java
index bb5679a70e..c8767dedb4 100644
--- a/documentation/user/en/query/examples/simplestQuery.java
+++ b/documentation/user/en/query/examples/simplestQuery.java
@@ -1,3 +1,33 @@
-query(
- collection("Product")
-)
\ No newline at end of file
+/*
+ *
+ * _ _ ____ ____
+ * _____ _(_) |_ __ _| _ \| __ )
+ * / _ \ \ / / | __/ _` | | | | _ \
+ * | __/\ V /| | || (_| | |_| | |_) |
+ * \___| \_/ |_|\__\__,_|____/|____/
+ *
+ * Copyright (c) 2025
+ *
+ * Licensed under the Business Source License, Version 1.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://github.com/FgForrest/evitaDB/blob/master/LICENSE
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+final EvitaResponse entities = evita.queryCatalog(
+ "evita",
+ session -> {
+ return session.querySealedEntity(
+ query(
+ collection("Product")
+ )
+ );
+ }
+);
\ No newline at end of file
diff --git a/documentation/user/en/query/header/examples/labels.evitaql b/documentation/user/en/query/header/examples/labels.evitaql
new file mode 100644
index 0000000000..2d0a337fe9
--- /dev/null
+++ b/documentation/user/en/query/header/examples/labels.evitaql
@@ -0,0 +1,10 @@
+query(
+ head(
+ collection("Product"),
+ label("query-name", "my-query"),
+ label("url", "/test-url"),
+ ),
+ filterBy(
+ entityPrimaryKeyInSet(1, 2, 3)
+ )
+)
\ No newline at end of file
diff --git a/documentation/user/en/query/header/examples/labels.graphql b/documentation/user/en/query/header/examples/labels.graphql
new file mode 100644
index 0000000000..6224b7fb91
--- /dev/null
+++ b/documentation/user/en/query/header/examples/labels.graphql
@@ -0,0 +1,17 @@
+{
+ queryProduct(
+ filterBy: {
+ entityPrimaryKeyInSet: [
+ 1,
+ 2,
+ 3
+ ]
+ }
+ ) {
+ recordPage {
+ data {
+ primaryKey
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/documentation/user/en/query/header/examples/labels.java b/documentation/user/en/query/header/examples/labels.java
new file mode 100644
index 0000000000..0df0034f41
--- /dev/null
+++ b/documentation/user/en/query/header/examples/labels.java
@@ -0,0 +1,40 @@
+/*
+ *
+ * _ _ ____ ____
+ * _____ _(_) |_ __ _| _ \| __ )
+ * / _ \ \ / / | __/ _` | | | | _ \
+ * | __/\ V /| | || (_| | |_| | |_) |
+ * \___| \_/ |_|\__\__,_|____/|____/
+ *
+ * Copyright (c) 2025
+ *
+ * Licensed under the Business Source License, Version 1.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://github.com/FgForrest/evitaDB/blob/master/LICENSE
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+final EvitaResponse entities = evita.queryCatalog(
+ "evita",
+ session -> {
+ return session.querySealedEntity(
+ query(
+ head(
+ collection("Product"),
+ label("query-name", "my-query"),
+ label("url", "/test-url")
+ ),
+ filterBy(
+ entityPrimaryKeyInSet(1, 2, 3)
+ )
+ )
+ );
+ }
+);
\ No newline at end of file
diff --git a/documentation/user/en/query/header/examples/labels.rest b/documentation/user/en/query/header/examples/labels.rest
new file mode 100644
index 0000000000..b95e417d0e
--- /dev/null
+++ b/documentation/user/en/query/header/examples/labels.rest
@@ -0,0 +1,11 @@
+POST /rest/evita/Product/query
+
+{
+ "filterBy" : {
+ "entityPrimaryKeyInSet" : [
+ 1,
+ 2,
+ 3
+ ]
+ }
+}
\ No newline at end of file
diff --git a/documentation/user/en/query/header/header.md b/documentation/user/en/query/header/header.md
index 72f9447ee8..76c23ba976 100644
--- a/documentation/user/en/query/header/header.md
+++ b/documentation/user/en/query/header/header.md
@@ -55,5 +55,14 @@ label(
This `label` constraint allows a single label name with associated value to be specified in the query header and
propagated to the trace generated for the query. A query can be tagged with multiple labels.
-Labels are also recorded with the query in the traffic record and can be used to look up the query in the traffic
-inspection or traffic replay. Labels are also attached to JFR events related to the query.
\ No newline at end of file
+Labels are also recorded with the query in the [traffic record](../../operate/observe.md#traffic-recording) and can be
+used to look up the query in the traffic inspection or traffic replay. Labels are also attached to JFR events related
+to the query.
+
+Each label is a key-value pair appended to the query header, as shown in the following example:
+
+
+
+[Attaching labels to query](/documentation/user/en/query/header/examples/labels.evitaql)
+
+
\ No newline at end of file
diff --git a/evita_functional_tests/src/test/java/io/evitadb/documentation/JavaPrettyPrintingVisitor.java b/evita_functional_tests/src/test/java/io/evitadb/documentation/JavaPrettyPrintingVisitor.java
index a8406335d5..ddbc6f7b07 100644
--- a/evita_functional_tests/src/test/java/io/evitadb/documentation/JavaPrettyPrintingVisitor.java
+++ b/evita_functional_tests/src/test/java/io/evitadb/documentation/JavaPrettyPrintingVisitor.java
@@ -6,7 +6,7 @@
* | __/\ V /| | || (_| | |_| | |_) |
* \___| \_/ |_|\__\__,_|____/|____/
*
- * Copyright (c) 2023-2024
+ * Copyright (c) 2023-2025
*
* Licensed under the Business Source License, Version 1.1 (the "License");
* you may not use this file except in compliance with the License.
@@ -190,7 +190,7 @@ public void traverse(@Nonnull Query query) {
}
this.result.append("query" + ARG_OPENING).append(newLine());
this.level = 1;
- ofNullable(query.getCollection()).ifPresent(it -> {
+ ofNullable(query.getHead()).ifPresent(it -> {
it.accept(this);
this.result.append(",");
});
diff --git a/evita_functional_tests/src/test/java/io/evitadb/documentation/UserDocumentationTest.java b/evita_functional_tests/src/test/java/io/evitadb/documentation/UserDocumentationTest.java
index 8e49deeb34..359b86258f 100644
--- a/evita_functional_tests/src/test/java/io/evitadb/documentation/UserDocumentationTest.java
+++ b/evita_functional_tests/src/test/java/io/evitadb/documentation/UserDocumentationTest.java
@@ -6,7 +6,7 @@
* | __/\ V /| | || (_| | |_| | |_) |
* \___| \_/ |_|\__\__,_|____/|____/
*
- * Copyright (c) 2023-2024
+ * Copyright (c) 2023-2025
*
* Licensed under the Business Source License, Version 1.1 (the "License");
* you may not use this file except in compliance with the License.
@@ -451,9 +451,9 @@ Stream testDocumentation() throws IOException {
Stream testSingleFileDocumentation() {
return this.createTests(
Environment.DEMO_SERVER,
- getRootDirectory().resolve("documentation/user/en/get-started/create-first-database.md"),
+ getRootDirectory().resolve("documentation/user/en/get-started/run-evitadb.md"),
new ExampleFilter[]{
- ExampleFilter.CSHARP,
+ /*ExampleFilter.CSHARP,*/
ExampleFilter.JAVA,
ExampleFilter.REST,
ExampleFilter.GRAPHQL,
@@ -473,8 +473,8 @@ Stream testSingleFileDocumentation() {
@Disabled
Stream testSingleFileDocumentationAndCreateOtherLanguageSnippets() {
return this.createTests(
- Environment.LOCALHOST,
- getRootDirectory().resolve("documentation/user/en/query/requirements/facet.md"),
+ Environment.DEMO_SERVER,
+ getRootDirectory().resolve("documentation/user/en/query/header/header.md"),
ExampleFilter.values(),
CreateSnippets.MARKDOWN, CreateSnippets.JAVA, CreateSnippets.GRAPHQL, CreateSnippets.REST
).stream();
diff --git a/evita_query/src/main/java/io/evitadb/api/query/parser/visitor/EvitaQLQueryVisitor.java b/evita_query/src/main/java/io/evitadb/api/query/parser/visitor/EvitaQLQueryVisitor.java
index 6b933e6c8e..ae91ff1cde 100644
--- a/evita_query/src/main/java/io/evitadb/api/query/parser/visitor/EvitaQLQueryVisitor.java
+++ b/evita_query/src/main/java/io/evitadb/api/query/parser/visitor/EvitaQLQueryVisitor.java
@@ -96,7 +96,7 @@ protected HeadConstraint findHeadConstraint(@Nonnull ParserRuleContext ctx, @Non
if (headConstraints.isEmpty()) {
return null;
}
- if ((headConstraints.size() > 1) || !(headConstraints.get(0) instanceof Collection)) {
+ if ((headConstraints.size() > 1) || !(headConstraints.get(0) instanceof HeadConstraint || headConstraints.get(0) instanceof Collection)) {
throw new EvitaSyntaxException(
ctx,
"Query can have only one top level head constraint."
From 5e0b7087c1a7e8d08aac6444b331cc65b57c9666 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Novotn=C3=BD?=
Date: Fri, 7 Feb 2025 14:52:50 +0100
Subject: [PATCH 15/22] fix: NPE fix
---
.../store/traffic/TrafficRecordingIndex.java | 20 ++++++++++---------
1 file changed, 11 insertions(+), 9 deletions(-)
diff --git a/evita_store/evita_store_server/src/main/java/io/evitadb/store/traffic/TrafficRecordingIndex.java b/evita_store/evita_store_server/src/main/java/io/evitadb/store/traffic/TrafficRecordingIndex.java
index 8ce1fae124..6c492cd8d8 100644
--- a/evita_store/evita_store_server/src/main/java/io/evitadb/store/traffic/TrafficRecordingIndex.java
+++ b/evita_store/evita_store_server/src/main/java/io/evitadb/store/traffic/TrafficRecordingIndex.java
@@ -669,15 +669,17 @@ private void emptyRemovalQueue() {
*/
private void removeSessionFromIndexes(long sessionSequenceOrder) {
final SessionDescriptor sessionDescriptor = this.sessionUuidIndex.remove(sessionSequenceOrder);
- this.sessionIdIndex.remove(sessionDescriptor.getSessionId());
- this.sessionCreationIndex.delete(sessionDescriptor.getCreated());
- this.sessionDurationIndex.delete(sessionDescriptor.getMaxDurationInMillis());
- this.sessionFetchCountIndex.delete(sessionDescriptor.getMaxFetchCount());
- this.sessionBytesFetchedIndex.delete(sessionDescriptor.getMaxBytesFetchedTotal());
- for (TrafficRecordingType recordingType : sessionDescriptor.getRecordingTypes()) {
- final TransactionalObjectBPlusTree typeIndex = this.sessionRecordingTypeIndex.get(recordingType);
- if (typeIndex != null) {
- typeIndex.delete(sessionSequenceOrder);
+ if (sessionDescriptor != null) {
+ this.sessionIdIndex.remove(sessionDescriptor.getSessionId());
+ this.sessionCreationIndex.delete(sessionDescriptor.getCreated());
+ this.sessionDurationIndex.delete(sessionDescriptor.getMaxDurationInMillis());
+ this.sessionFetchCountIndex.delete(sessionDescriptor.getMaxFetchCount());
+ this.sessionBytesFetchedIndex.delete(sessionDescriptor.getMaxBytesFetchedTotal());
+ for (TrafficRecordingType recordingType : sessionDescriptor.getRecordingTypes()) {
+ final TransactionalObjectBPlusTree typeIndex = this.sessionRecordingTypeIndex.get(recordingType);
+ if (typeIndex != null) {
+ typeIndex.delete(sessionSequenceOrder);
+ }
}
}
}
From f6f32dc275bb3f5c043819d7938c5e3d37284b52 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Novotn=C3=BD?=
Date: Fri, 7 Feb 2025 14:57:38 +0100
Subject: [PATCH 16/22] doc: updated main menu
---
documentation/user/en/menu.json | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/documentation/user/en/menu.json b/documentation/user/en/menu.json
index 1e34d73221..6f4e7c8f51 100644
--- a/documentation/user/en/menu.json
+++ b/documentation/user/en/menu.json
@@ -94,6 +94,10 @@
"title": "Basics",
"path": "/query/basics.md"
},
+ {
+ "title": "Header",
+ "path": "/query/filtering/behavioral.md"
+ },
{
"title": "Filtering",
"children": [
From 513abcd822363c47e561b87279b17648da146b19 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Novotn=C3=BD?=
Date: Fri, 7 Feb 2025 15:19:47 +0100
Subject: [PATCH 17/22] doc: graphQL / REST snippets for labels
---
.../en/query/header/examples/labels.graphql | 14 ++++
.../user/en/query/header/examples/labels.rest | 14 ++++
.../java/io/evitadb/api/query/head/Label.java | 3 +
.../query/HeadConstraintToJsonConverter.java | 65 +++++++++++++++++++
.../query/graphql/GraphQLQueryConverter.java | 20 +++++-
.../client/query/rest/RestQueryConverter.java | 19 +++++-
6 files changed, 130 insertions(+), 5 deletions(-)
create mode 100644 evita_test_support/src/main/java/io/evitadb/test/client/query/HeadConstraintToJsonConverter.java
diff --git a/documentation/user/en/query/header/examples/labels.graphql b/documentation/user/en/query/header/examples/labels.graphql
index 6224b7fb91..f757ebf9dc 100644
--- a/documentation/user/en/query/header/examples/labels.graphql
+++ b/documentation/user/en/query/header/examples/labels.graphql
@@ -1,5 +1,19 @@
{
queryProduct(
+ head: [
+ {
+ label: {
+ name: "query-name",
+ value: "my-query"
+ }
+ },
+ {
+ label: {
+ name: "url",
+ value: "/test-url"
+ }
+ }
+ ],
filterBy: {
entityPrimaryKeyInSet: [
1,
diff --git a/documentation/user/en/query/header/examples/labels.rest b/documentation/user/en/query/header/examples/labels.rest
index b95e417d0e..94b3eb6e51 100644
--- a/documentation/user/en/query/header/examples/labels.rest
+++ b/documentation/user/en/query/header/examples/labels.rest
@@ -1,6 +1,20 @@
POST /rest/evita/Product/query
{
+ "head" : [
+ {
+ "label" : {
+ "name" : "query-name",
+ "value" : "my-query"
+ }
+ },
+ {
+ "label" : {
+ "name" : "url",
+ "value" : "/test-url"
+ }
+ }
+ ],
"filterBy" : {
"entityPrimaryKeyInSet" : [
1,
diff --git a/evita_query/src/main/java/io/evitadb/api/query/head/Label.java b/evita_query/src/main/java/io/evitadb/api/query/head/Label.java
index 7808fd2f75..c6c9ebcd09 100644
--- a/evita_query/src/main/java/io/evitadb/api/query/head/Label.java
+++ b/evita_query/src/main/java/io/evitadb/api/query/head/Label.java
@@ -26,6 +26,7 @@
import io.evitadb.api.query.GenericConstraint;
import io.evitadb.api.query.HeadConstraint;
+import io.evitadb.api.query.descriptor.annotation.AliasForParameter;
import io.evitadb.api.query.descriptor.annotation.ConstraintDefinition;
import io.evitadb.api.query.descriptor.annotation.Creator;
import io.evitadb.dataType.EvitaDataTypes;
@@ -92,6 +93,7 @@ public & Serializable> Label(@Nullable String name, @Nu
/**
* Returns the name of the label.
*/
+ @AliasForParameter("name")
@Nonnull
public String getLabelName() {
final Serializable[] args = getArguments();
@@ -102,6 +104,7 @@ public String getLabelName() {
/**
* Returns the value of the label.
*/
+ @AliasForParameter("value")
@Nonnull
public Serializable getLabelValue() {
final Serializable[] args = getArguments();
diff --git a/evita_test_support/src/main/java/io/evitadb/test/client/query/HeadConstraintToJsonConverter.java b/evita_test_support/src/main/java/io/evitadb/test/client/query/HeadConstraintToJsonConverter.java
new file mode 100644
index 0000000000..2ec396792a
--- /dev/null
+++ b/evita_test_support/src/main/java/io/evitadb/test/client/query/HeadConstraintToJsonConverter.java
@@ -0,0 +1,65 @@
+/*
+ *
+ * _ _ ____ ____
+ * _____ _(_) |_ __ _| _ \| __ )
+ * / _ \ \ / / | __/ _` | | | | _ \
+ * | __/\ V /| | || (_| | |_| | |_) |
+ * \___| \_/ |_|\__\__,_|____/|____/
+ *
+ * Copyright (c) 2023-2025
+ *
+ * Licensed under the Business Source License, Version 1.1 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://github.com/FgForrest/evitaDB/blob/master/LICENSE
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.evitadb.test.client.query;
+
+import io.evitadb.api.query.Constraint;
+import io.evitadb.api.query.descriptor.ConstraintDescriptor;
+import io.evitadb.api.query.descriptor.ConstraintDescriptorProvider;
+import io.evitadb.api.query.head.Head;
+import io.evitadb.api.requestResponse.schema.CatalogSchemaContract;
+
+import javax.annotation.Nonnull;
+import java.util.function.Predicate;
+
+import static io.evitadb.utils.CollectionUtils.createHashMap;
+
+/**
+ * Constraint converter for {@link io.evitadb.api.query.HeadConstraint}s.
+ *
+ * @author Lukáš Hornych, FG Forrest a.s. (c) 2025
+ */
+public class HeadConstraintToJsonConverter extends ConstraintToJsonConverter {
+
+ public HeadConstraintToJsonConverter(@Nonnull CatalogSchemaContract catalogSchema) {
+ super(
+ catalogSchema,
+ createHashMap(0) // currently, we don't support any filter constraint with additional children
+ );
+ }
+
+ public HeadConstraintToJsonConverter(@Nonnull CatalogSchemaContract catalogSchema,
+ @Nonnull Predicate>> constraintPredicate) {
+ super(
+ catalogSchema,
+ constraintPredicate,
+ createHashMap(0) // currently, we don't support any filter constraint with additional children
+ );
+ }
+
+ @Nonnull
+ @Override
+ protected ConstraintDescriptor getDefaultRootConstraintContainerDescriptor() {
+ return ConstraintDescriptorProvider.getConstraint(Head.class);
+ }
+}
diff --git a/evita_test_support/src/main/java/io/evitadb/test/client/query/graphql/GraphQLQueryConverter.java b/evita_test_support/src/main/java/io/evitadb/test/client/query/graphql/GraphQLQueryConverter.java
index 877238dd22..3a7604184b 100644
--- a/evita_test_support/src/main/java/io/evitadb/test/client/query/graphql/GraphQLQueryConverter.java
+++ b/evita_test_support/src/main/java/io/evitadb/test/client/query/graphql/GraphQLQueryConverter.java
@@ -6,7 +6,7 @@
* | __/\ V /| | || (_| | |_| | |_) |
* \___| \_/ |_|\__\__,_|____/|____/
*
- * Copyright (c) 2023-2024
+ * Copyright (c) 2023-2025
*
* Licensed under the Business Source License, Version 1.1 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,11 +26,14 @@
import io.evitadb.api.EvitaContract;
import io.evitadb.api.EvitaSessionContract;
import io.evitadb.api.query.Constraint;
+import io.evitadb.api.query.ConstraintContainer;
+import io.evitadb.api.query.HeadConstraint;
import io.evitadb.api.query.Query;
import io.evitadb.api.query.QueryUtils;
import io.evitadb.api.query.filter.EntityLocaleEquals;
-import io.evitadb.api.query.filter.EntityScope;
+import io.evitadb.api.query.head.Collection;
import io.evitadb.api.query.require.*;
+import io.evitadb.api.query.visitor.ConstraintCloneVisitor;
import io.evitadb.api.requestResponse.schema.CatalogSchemaContract;
import io.evitadb.externalApi.api.catalog.dataApi.constraint.EntityDataLocator;
import io.evitadb.externalApi.api.catalog.dataApi.constraint.GenericDataLocator;
@@ -38,6 +41,7 @@
import io.evitadb.externalApi.api.catalog.dataApi.model.DataChunkDescriptor;
import io.evitadb.externalApi.api.catalog.dataApi.model.ResponseDescriptor;
import io.evitadb.test.client.query.FilterConstraintToJsonConverter;
+import io.evitadb.test.client.query.HeadConstraintToJsonConverter;
import io.evitadb.test.client.query.JsonConstraint;
import io.evitadb.test.client.query.OrderConstraintToJsonConverter;
import io.evitadb.test.client.query.RequireConstraintToJsonConverter;
@@ -104,6 +108,7 @@ public String convert(@Nonnull EvitaContract evita, @Nonnull String catalogName,
@Nonnull
private String convertHeader(@Nonnull CatalogSchemaContract catalogSchema, @Nonnull Query query, @Nonnull String entityType) {
+ final HeadConstraintToJsonConverter headConstraintToJsonConverter = new HeadConstraintToJsonConverter(catalogSchema);
final FilterConstraintToJsonConverter filterConstraintToJsonConverter = new FilterConstraintToJsonConverter(catalogSchema);
final OrderConstraintToJsonConverter orderConstraintToJsonConverter = new OrderConstraintToJsonConverter(catalogSchema);
final RequireConstraintToJsonConverter requireConstraintToJsonConverter = new RequireConstraintToJsonConverter(
@@ -113,7 +118,16 @@ private String convertHeader(@Nonnull CatalogSchemaContract catalogSchema, @Nonn
new AtomicReference<>(orderConstraintToJsonConverter)
);
- final List rootConstraints = new ArrayList<>(3);
+ final List rootConstraints = new ArrayList<>(4);
+ if (query.getHead() != null) {
+ final HeadConstraint head = ConstraintCloneVisitor.clone(query.getHead(), (visitor, theConstraint) -> theConstraint instanceof Collection ? null : theConstraint);
+ if (head != null && (!(head instanceof ConstraintContainer> cc) || cc.getChildrenCount() > 0)) {
+ rootConstraints.add(
+ headConstraintToJsonConverter.convert(new GenericDataLocator(new ManagedEntityTypePointer(entityType)), head)
+ .orElseThrow(() -> new IllegalStateException("Root JSON head constraint cannot be null if original query has head constraint."))
+ );
+ }
+ }
if (query.getFilterBy() != null) {
rootConstraints.add(
filterConstraintToJsonConverter.convert(new EntityDataLocator(new ManagedEntityTypePointer(entityType)), query.getFilterBy())
diff --git a/evita_test_support/src/main/java/io/evitadb/test/client/query/rest/RestQueryConverter.java b/evita_test_support/src/main/java/io/evitadb/test/client/query/rest/RestQueryConverter.java
index e4dbc72244..6d1d627342 100644
--- a/evita_test_support/src/main/java/io/evitadb/test/client/query/rest/RestQueryConverter.java
+++ b/evita_test_support/src/main/java/io/evitadb/test/client/query/rest/RestQueryConverter.java
@@ -6,7 +6,7 @@
* | __/\ V /| | || (_| | |_| | |_) |
* \___| \_/ |_|\__\__,_|____/|____/
*
- * Copyright (c) 2023-2024
+ * Copyright (c) 2023-2025
*
* Licensed under the Business Source License, Version 1.1 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,12 +27,17 @@
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.evitadb.api.EvitaContract;
import io.evitadb.api.EvitaSessionContract;
+import io.evitadb.api.query.ConstraintContainer;
+import io.evitadb.api.query.HeadConstraint;
import io.evitadb.api.query.Query;
+import io.evitadb.api.query.head.Collection;
+import io.evitadb.api.query.visitor.ConstraintCloneVisitor;
import io.evitadb.api.requestResponse.schema.CatalogSchemaContract;
import io.evitadb.externalApi.api.catalog.dataApi.constraint.EntityDataLocator;
import io.evitadb.externalApi.api.catalog.dataApi.constraint.GenericDataLocator;
import io.evitadb.externalApi.api.catalog.dataApi.constraint.ManagedEntityTypePointer;
import io.evitadb.test.client.query.FilterConstraintToJsonConverter;
+import io.evitadb.test.client.query.HeadConstraintToJsonConverter;
import io.evitadb.test.client.query.JsonConstraint;
import io.evitadb.test.client.query.OrderConstraintToJsonConverter;
import io.evitadb.test.client.query.RequireConstraintToJsonConverter;
@@ -97,6 +102,7 @@ private String resolveHeader(@Nonnull String catalogName, @Nonnull String entity
@Nonnull
private String convertBody(@Nonnull CatalogSchemaContract catalogSchema, @Nonnull Query query, @Nonnull String entityType) {
+ final HeadConstraintToJsonConverter headConstraintToJsonConverter = new HeadConstraintToJsonConverter(catalogSchema);
final FilterConstraintToJsonConverter filterConstraintToJsonConverter = new FilterConstraintToJsonConverter(catalogSchema);
final OrderConstraintToJsonConverter orderConstraintToJsonConverter = new OrderConstraintToJsonConverter(catalogSchema);
final RequireConstraintToJsonConverter requireConstraintToJsonConverter = new RequireConstraintToJsonConverter(
@@ -106,7 +112,16 @@ private String convertBody(@Nonnull CatalogSchemaContract catalogSchema, @Nonnul
);
final ObjectNode body = jsonNodeFactory.objectNode();
- final List rootConstraints = new ArrayList<>(3);
+ final List rootConstraints = new ArrayList<>(4);
+ if (query.getHead() != null) {
+ final HeadConstraint head = ConstraintCloneVisitor.clone(query.getHead(), (visitor, theConstraint) -> theConstraint instanceof Collection ? null : theConstraint);
+ if (head != null && (!(head instanceof ConstraintContainer> cc) || cc.getChildrenCount() > 0)) {
+ rootConstraints.add(
+ headConstraintToJsonConverter.convert(new GenericDataLocator(new ManagedEntityTypePointer(entityType)), head)
+ .orElseThrow(() -> new IllegalStateException("Root JSON head constraint cannot be null if original query has head constraint."))
+ );
+ }
+ }
if (query.getFilterBy() != null) {
rootConstraints.add(
filterConstraintToJsonConverter.convert(new EntityDataLocator(new ManagedEntityTypePointer(entityType)), query.getFilterBy())
From 77c1fae04e9a89cd29a5eb065c97d01a3ef373a9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Novotn=C3=BD?=
Date: Fri, 7 Feb 2025 15:22:24 +0100
Subject: [PATCH 18/22] doc: updated main menu
---
documentation/user/en/menu.json | 11 ++++++-
.../user/en/query/header/collection.md | 30 +++++++++++++++++++
.../en/query/header/{header.md => label.md} | 30 ++-----------------
3 files changed, 43 insertions(+), 28 deletions(-)
create mode 100644 documentation/user/en/query/header/collection.md
rename documentation/user/en/query/header/{header.md => label.md} (52%)
diff --git a/documentation/user/en/menu.json b/documentation/user/en/menu.json
index 6f4e7c8f51..db2a8e92ee 100644
--- a/documentation/user/en/menu.json
+++ b/documentation/user/en/menu.json
@@ -96,7 +96,16 @@
},
{
"title": "Header",
- "path": "/query/filtering/behavioral.md"
+ "children": [
+ {
+ "title": "Collection",
+ "path": "/query/header/collection.md"
+ },
+ {
+ "title": "Label",
+ "path": "/query/header/label.md"
+ }
+ ]
},
{
"title": "Filtering",
diff --git a/documentation/user/en/query/header/collection.md b/documentation/user/en/query/header/collection.md
new file mode 100644
index 0000000000..8b79343114
--- /dev/null
+++ b/documentation/user/en/query/header/collection.md
@@ -0,0 +1,30 @@
+---
+title: Collection
+date: '12.12.2024'
+perex: Only a few constraints can be used in the header part of the query. Collection defines the query target.
+author: 'Ing. Jan Novotný'
+proofreading: 'done'
+preferredLang: 'evitaql'
+---
+
+## Collection
+
+```evitaql-syntax
+collection(
+ argument:string!
+)
+```
+
+
+
argument:string!
+
+ mandatory string argument representing the name of the entity collection to be queried
+
+
+
+This constraint defines entity collection targeted by this query.
+Target entity collection definition is defined as part of a GraphQL query namean endpoint URL.
+It can be omitted when using generic GraphQL queryendpoint
+if the [filterBy](../basics#filter-by) contains a constraint that targets a globally unique attribute.
+This is useful for one of the most important e-commerce scenarios, where the requested URI needs to match one of the
+existing entities (see the [routing](../../solve/routing.md) chapter for a detailed guide).
\ No newline at end of file
diff --git a/documentation/user/en/query/header/header.md b/documentation/user/en/query/header/label.md
similarity index 52%
rename from documentation/user/en/query/header/header.md
rename to documentation/user/en/query/header/label.md
index 76c23ba976..249b2bbcce 100644
--- a/documentation/user/en/query/header/header.md
+++ b/documentation/user/en/query/header/label.md
@@ -1,36 +1,12 @@
---
-title: Headers
+title: Label
date: '12.12.2024'
-perex: |
- Only a few constraints can be used in the header part of the query. They define the query target or may be used to
- to tag the query for later identification.
+perex: Labels allow tagging the query for later identification.
author: 'Ing. Jan Novotný'
proofreading: 'done'
preferredLang: 'evitaql'
---
-## Collection
-
-```evitaql-syntax
-collection(
- argument:string!
-)
-```
-
-
-
argument:string!
-
- mandatory string argument representing the name of the entity collection to be queried
-
-
-
-This constraint defines entity collection targeted by this query.
-Target entity collection definition is defined as part of a GraphQL query namean endpoint URL.
-It can be omitted when using generic GraphQL queryendpoint
-if the [filterBy](../basics#filter-by) contains a constraint that targets a globally unique attribute.
-This is useful for one of the most important e-commerce scenarios, where the requested URI needs to match one of the
-existing entities (see the [routing](../../solve/routing.md) chapter for a detailed guide).
-
## Label
```evitaql-syntax
@@ -56,7 +32,7 @@ This `label` constraint allows a single label name with associated value to be s
propagated to the trace generated for the query. A query can be tagged with multiple labels.
Labels are also recorded with the query in the [traffic record](../../operate/observe.md#traffic-recording) and can be
-used to look up the query in the traffic inspection or traffic replay. Labels are also attached to JFR events related
+used to look up the query in the traffic inspection or traffic replay. Labels are also attached to JFR events related
to the query.
Each label is a key-value pair appended to the query header, as shown in the following example:
From 05fa055c41a6e0096cb9c8556ed8fc0db31aa43c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Novotn=C3=BD?=
Date: Fri, 7 Feb 2025 16:51:43 +0100
Subject: [PATCH 19/22] doc: excluding unfinished blog post
---
documentation/{blog/en => }/16-traffic-recording.draft | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename documentation/{blog/en => }/16-traffic-recording.draft (100%)
diff --git a/documentation/blog/en/16-traffic-recording.draft b/documentation/16-traffic-recording.draft
similarity index 100%
rename from documentation/blog/en/16-traffic-recording.draft
rename to documentation/16-traffic-recording.draft
From afb2a0ef38d2cffcb0f96846b12dc07f263e170a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Novotn=C3=BD?=
Date: Fri, 7 Feb 2025 17:28:09 +0100
Subject: [PATCH 20/22] doc: marking post as draft
---
.../en/16-traffic-recording.md} | 1 +
1 file changed, 1 insertion(+)
rename documentation/{16-traffic-recording.draft => blog/en/16-traffic-recording.md} (99%)
diff --git a/documentation/16-traffic-recording.draft b/documentation/blog/en/16-traffic-recording.md
similarity index 99%
rename from documentation/16-traffic-recording.draft
rename to documentation/blog/en/16-traffic-recording.md
index 3f0ffe4f07..7c77089f45 100644
--- a/documentation/16-traffic-recording.draft
+++ b/documentation/blog/en/16-traffic-recording.md
@@ -6,6 +6,7 @@ date: '24.01.2025'
author: 'Ing. Jan Novotný'
motive: assets/images/16-traffic-recording.png
proofreading: 'done'
+draft: true
---
In the latest version `2025.1` of evitaDB we have introduced a new feature called `Traffic Recording`. It's disabled by default, but you can easily enable it by setting `server.trafficRecording.enabled` to `true`. Even if you don't do this, you can always start it manually in the evitaLab console. This feature allows you to record all traffic that passes through the database - sessions, queries, mutations, entity fetches - everything.
From c4b87b6dcfb310e8a4c7f2628de81274ed221fb6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Novotn=C3=BD?=
Date: Fri, 7 Feb 2025 21:14:09 +0100
Subject: [PATCH 21/22] doc: traffic recording blog post
---
documentation/blog/en/16-traffic-recording.md | 25 ++++++++++++++++--
.../images/16-traffic-recording.full.png | Bin 0 -> 1767201 bytes
.../en/assets/images/16-traffic-recording.png | Bin 0 -> 153066 bytes
3 files changed, 23 insertions(+), 2 deletions(-)
create mode 100644 documentation/blog/en/assets/images/16-traffic-recording.full.png
create mode 100644 documentation/blog/en/assets/images/16-traffic-recording.png
diff --git a/documentation/blog/en/16-traffic-recording.md b/documentation/blog/en/16-traffic-recording.md
index 7c77089f45..18fd728fbb 100644
--- a/documentation/blog/en/16-traffic-recording.md
+++ b/documentation/blog/en/16-traffic-recording.md
@@ -6,11 +6,18 @@ date: '24.01.2025'
author: 'Ing. Jan Novotný'
motive: assets/images/16-traffic-recording.png
proofreading: 'done'
-draft: true
---
+
In the latest version `2025.1` of evitaDB we have introduced a new feature called `Traffic Recording`. It's disabled by default, but you can easily enable it by setting `server.trafficRecording.enabled` to `true`. Even if you don't do this, you can always start it manually in the evitaLab console. This feature allows you to record all traffic that passes through the database - sessions, queries, mutations, entity fetches - everything.
-The capture engine is designed to be as lightweight as possible, so you can run it in production without noticing any performance degradation. If the traffic is heavy, you can also configure the recording engine to sample the traffic, reducing the amount of data stored, but still enough to analyse traffic patterns. Traffic data is serialised in a limited buffer in memory, which is very fast, and then flushed asynchronously to the disk buffer file. If the flushing process can't keep up with the traffic, the recording engine will automatically discard any traffic that doesn't fit into the memory buffer. So the worst-case scenario is that you'll lose some data for analysis, but the database will still work fine and your customers won't notice a thing.
+The capture engine is designed to be as lightweight as possible, so that you can run the tool in the production. If the traffic is heavy, you can also configure the recording engine to sample the traffic, reducing the amount of data stored and limit the performance impact, but still enough to analyse traffic patterns. Traffic data is serialised in a limited buffer in memory, which is quite fast, and then flushed asynchronously to the disk buffer file. If the flushing process can't keep up with the traffic, the recording engine will automatically discard any traffic that doesn't fit into the memory buffer. So the worst-case scenario is that you'll lose some data for analysis, but the database will still work fine and your customers won't notice a thing.
+
+
+
+
The disk buffer is designed to behave like a ring buffer and is allocated at the start of the database, so you can't run out of disk space. When the end of the buffer is reached, the recording engine starts to overwrite the oldest data from the beginning. You can start another asynchronous task that will capture all the data from the disk buffer file before it's overwritten into another compressed file for later analysis. This task can be set to stop automatically when it reaches a certain data size or after a certain period of time.
@@ -24,6 +31,13 @@ With Traffic Recording, evitaDB can be set up to automatically capture all traff
Traffic Recorder captures queries in their original format (GraphQL, REST, gRPC) as well as in the internal evitaQL format. Often a single GraphQL query can represent several evitaQL queries that are combined into a single result and actually executed in parallel by the query engine. All these relationships are captured and visualised in the evitaLab interface. All input queries are available in the exact form in which they arrived in the database, so you can access variables, fragments, etc. as they were sent by the client.
+
+
+
+
The traffic recorder also records all invalid requests, so you can see requests that couldn't be parsed or were rejected by the database for some reason. This can be very useful for debugging and improving the client application.
The filter allows you to filter queries by various criteria - for example, you can filter only queries that took more than a certain amount of time to execute, or only queries that fetched excessive amounts of data from disk. This can help you identify performance bottlenecks in your application early in the development process.
@@ -32,6 +46,13 @@ The filter allows you to filter queries by various criteria - for example, you c
Because you can capture a representative sample of traffic, you can use it to analyse the performance of your application. You can see which queries are the most frequent, which are the slowest, which are fetching the most data, etc. You can restore backups of your production data and replay the captured traffic on different machines to test different hardware configurations, or stress test the database by increasing the replay speed or multiplying it by parallelizing clients replaying the traffic.
+