From e70f3b9d30f034dd6b3a03211629b204ca38b5c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Thu, 7 Dec 2023 14:29:52 +0100 Subject: [PATCH 01/52] docs: constraint links to user docs --- documentation/user/en/query/basics.md | 18 +- .../user/en/query/requirements/fetching.md | 215 +++++++++++------- .../constraint/ConstraintSchemaBuilder.java | 4 +- .../descriptor/ConstraintDescriptor.java | 108 +++++++-- .../query/descriptor/ConstraintProcessor.java | 1 + .../annotation/ConstraintDefinition.java | 18 +- .../java/io/evitadb/api/query/filter/And.java | 1 + .../api/query/filter/AttributeBetween.java | 1 + .../api/query/filter/AttributeContains.java | 1 + .../api/query/filter/AttributeEndsWith.java | 1 + .../api/query/filter/AttributeEquals.java | 1 + .../query/filter/AttributeGreaterThan.java | 1 + .../filter/AttributeGreaterThanEquals.java | 1 + .../api/query/filter/AttributeInRange.java | 1 + .../api/query/filter/AttributeInSet.java | 1 + .../evitadb/api/query/filter/AttributeIs.java | 1 + .../api/query/filter/AttributeLessThan.java | 1 + .../query/filter/AttributeLessThanEquals.java | 1 + .../api/query/filter/AttributeStartsWith.java | 1 + .../api/query/filter/EntityHaving.java | 1 + .../api/query/filter/EntityLocaleEquals.java | 1 + .../query/filter/EntityPrimaryKeyInSet.java | 1 + .../evitadb/api/query/filter/FacetHaving.java | 1 + .../io/evitadb/api/query/filter/FilterBy.java | 3 +- .../api/query/filter/FilterGroupBy.java | 1 + .../query/filter/HierarchyDirectRelation.java | 1 + .../api/query/filter/HierarchyExcluding.java | 1 + .../query/filter/HierarchyExcludingRoot.java | 1 + .../api/query/filter/HierarchyHaving.java | 1 + .../api/query/filter/HierarchyWithin.java | 1 + .../api/query/filter/HierarchyWithinRoot.java | 1 + .../java/io/evitadb/api/query/filter/Not.java | 1 + .../java/io/evitadb/api/query/filter/Or.java | 1 + .../api/query/filter/PriceBetween.java | 1 + .../api/query/filter/PriceInCurrency.java | 1 + .../api/query/filter/PriceInPriceLists.java | 1 + .../api/query/filter/PriceValidIn.java | 1 + .../api/query/filter/ReferenceHaving.java | 1 + .../evitadb/api/query/filter/UserFilter.java | 1 + .../io/evitadb/api/query/head/Collection.java | 1 + .../api/query/order/AttributeNatural.java | 1 + .../api/query/order/AttributeSetExact.java | 1 + .../api/query/order/AttributeSetInFilter.java | 1 + .../api/query/order/EntityGroupProperty.java | 1 + .../query/order/EntityPrimaryKeyExact.java | 1 + .../query/order/EntityPrimaryKeyInFilter.java | 1 + .../query/order/EntityPrimaryKeyNatural.java | 1 + .../api/query/order/EntityProperty.java | 1 + .../io/evitadb/api/query/order/OrderBy.java | 17 +- .../evitadb/api/query/order/OrderGroupBy.java | 1 + .../evitadb/api/query/order/PriceNatural.java | 1 + .../io/evitadb/api/query/order/Random.java | 1 + .../api/query/order/ReferenceProperty.java | 1 + .../query/require/AssociatedDataContent.java | 1 + .../api/query/require/AttributeContent.java | 1 + .../api/query/require/AttributeHistogram.java | 1 + .../api/query/require/DataInLocales.java | 1 + .../api/query/require/EntityFetch.java | 1 + .../api/query/require/EntityGroupFetch.java | 1 + .../query/require/FacetGroupsConjunction.java | 3 +- .../query/require/FacetGroupsDisjunction.java | 3 +- .../query/require/FacetGroupsNegation.java | 3 +- .../api/query/require/FacetSummary.java | 3 +- .../require/FacetSummaryOfReference.java | 3 +- .../api/query/require/HierarchyChildren.java | 1 + .../api/query/require/HierarchyContent.java | 1 + .../api/query/require/HierarchyDistance.java | 1 + .../api/query/require/HierarchyFromNode.java | 1 + .../api/query/require/HierarchyFromRoot.java | 1 + .../api/query/require/HierarchyLevel.java | 1 + .../api/query/require/HierarchyNode.java | 1 + .../query/require/HierarchyOfReference.java | 3 +- .../api/query/require/HierarchyOfSelf.java | 3 +- .../api/query/require/HierarchyParents.java | 1 + .../api/query/require/HierarchySiblings.java | 1 + .../query/require/HierarchyStatistics.java | 1 + .../api/query/require/HierarchyStopAt.java | 1 + .../io/evitadb/api/query/require/Page.java | 3 +- .../api/query/require/PriceContent.java | 1 + .../api/query/require/PriceHistogram.java | 3 +- .../evitadb/api/query/require/PriceType.java | 3 +- .../api/query/require/QueryTelemetry.java | 3 +- .../api/query/require/ReferenceContent.java | 1 + .../io/evitadb/api/query/require/Require.java | 3 +- .../io/evitadb/api/query/require/Strip.java | 3 +- 85 files changed, 353 insertions(+), 134 deletions(-) diff --git a/documentation/user/en/query/basics.md b/documentation/user/en/query/basics.md index 3cf2fd02a..672feea90 100644 --- a/documentation/user/en/query/basics.md +++ b/documentation/user/en/query/basics.md @@ -404,7 +404,7 @@ the resulting output to only include values that satisfy the constraint. ### String constraints -String constraints are similar to [Comparable](#comparable-constraints), but operate only on the +String constraints are similar to [Comparable](#comparable-constraints), but operate only on the [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html)[string](https://learn.microsoft.com/en-us/dotnet/api/system.string) attribute datatype and allow operations specific to it: @@ -448,6 +448,7 @@ filtering of entities by the fact that they refer to a particular part of the tr - [hierarchy within root](filtering/hierarchy.md#hierarchy-within-root) - [excluding root](filtering/hierarchy.md#excluding-root) - [excluding](filtering/hierarchy.md#excluding) +- [having](filtering/hierarchy.md#having) - [direct relation](filtering/hierarchy.md#direct-relation) ### Special constraints @@ -462,10 +463,11 @@ calculations: Order constraints allow you to define a rule that controls the order of entities in the response. It's similar to the "order by" clause in SQL. Currently, these ordering constraints are available for use: -- [entityPrimaryKeyInFilter](ordering/constant.md#exact-entity-primary-key-order-used-in-filter) -- [entityPrimaryKeyExact](ordering/constant.md#exact-entity-primary-key-order) -- [attributeSetInFilter](ordering/constant.md#exact-entity-attribute-value-order-used-in-filter) -- [attributeSetExact](ordering/constant.md#exact-entity-attribute-value-order) +- [entity primary key in filter](ordering/constant.md#exact-entity-primary-key-order-used-in-filter) +- [entity primary key exact](ordering/constant.md#exact-entity-primary-key-order) +- [entity primary key natural](ordering/comparable.md#primary-key-natural) +- [attribute set in filter](ordering/constant.md#exact-entity-attribute-value-order-used-in-filter) +- [attribute set exact](ordering/constant.md#exact-entity-attribute-value-order) - [attribute natural](ordering/comparable.md#attribute-natural) - [price natural](ordering/price.md#price-natural) - [reference property](ordering/reference.md#reference-property) @@ -502,13 +504,14 @@ is returned in query response. In order an entity body is ret of it: - [entity fetch](requirements/fetching.md#entity-fetch) +- [entity group fetch](requirements/fetching.md#entity-group-fetch) - [attribute content](requirements/fetching.md#attribute-content) - [associated data content](requirements/fetching.md#associated-data-content) - [price content](requirements/fetching.md#price-content) - [reference content](requirements/fetching.md#reference-content) - [hierarchy content](requirements/fetching.md#hierarchy-content) -- [data in locale](requirements/fetching.md#data-in-locale) +- [data in locale](requirements/fetching.md#data-in-locales) ### Hierarchy @@ -537,6 +540,9 @@ the summary could include a calculation of how many entities will be left when t the filter: - [facet summary](requirements/facet.md#facet-summary) + +- [facet summary of reference](requirements/facet.md#facet-summary-of-reference) + - [facet conjunction](requirements/facet.md#facet-groups-conjunction) - [facet disjunction](requirements/facet.md#facet-groups-disjunction) - [facet negation](requirements/facet.md#facet-groups-negation) diff --git a/documentation/user/en/query/requirements/fetching.md b/documentation/user/en/query/requirements/fetching.md index 1aa789293..c9114aaf9 100644 --- a/documentation/user/en/query/requirements/fetching.md +++ b/documentation/user/en/query/requirements/fetching.md @@ -1,8 +1,8 @@ --- title: Fetching perex: | - Fetch request constraints help control the amount of data returned in the query response. This technique is used to - reduce the amount of data transferred over the network and to reduce the load on the server. Fetching is similar to + Fetch request constraints help control the amount of data returned in the query response. This technique is used to + reduce the amount of data transferred over the network and to reduce the load on the server. Fetching is similar to joins and column selection in SQL, but is inspired by data fetching in the GraphQL protocol by incrementally following the relationships in the data. date: '23.7.2023' @@ -29,7 +29,7 @@ entityFetch( associatedDataContentAll| dataInLocales| dataInLocalesAll| - hierarchyContent| + hierarchyContent| priceContent| priceContentAll| priceContentRespectingFilter| @@ -37,14 +37,14 @@ entityFetch( referenceContentWithAttributes| referenceContentAll| referenceContentAllWithAttributes - )* + )* ) ```
requireConstraint:(...)*
- optional one or more constraints allowing you to instruct evitaDB to fetch the entity contents; + optional one or more constraints allowing you to instruct evitaDB to fetch the entity contents; one or all of the constraints may be present:
  • [attributeContent](#attribute-content)
  • @@ -75,11 +75,64 @@ entities have higher chance to stay in the cache). In the Java APIIn the C# client, including the `entityFetch` requirement in the query changes the output type in the response container. -Instead of returning an evita_api/src/main/java/io/evitadb/api/requestResponse/data/structure/EntityReference.javaEvitaDB.Client/Models/Data/Structure/EntityReference.cs +Instead of returning an evita_api/src/main/java/io/evitadb/api/requestResponse/data/structure/EntityReference.javaEvitaDB.Client/Models/Data/Structure/EntityReference.cs for each entity, the evita_api/src/main/java/io/evitadb/api/requestResponse/data/SealedEntity.javaEvitaDB.Client/Models/Data/ISealedEntity.cs type is returned. + + +## Entity group fetch + +```evitaql-syntax +entityGroupFetch( + requireConstraint:( + attributeContent| + attributeContentAll| + associatedDataContent| + associatedDataContentAll| + dataInLocales| + dataInLocalesAll| + hierarchyContent| + priceContent| + priceContentAll| + priceContentRespectingFilter| + referenceContent| + referenceContentWithAttributes| + referenceContentAll| + referenceContentAllWithAttributes + )* +) +``` + +
    +
    requireConstraint:(...)*
    +
    + optional one or more constraints allowing you to instruct evitaDB to fetch the group entity contents; + one or all of the constraints may be present: +
      +
    • [attributeContent](#attribute-content)
    • +
    • [attributeContentAll](#attribute-content-all)
    • +
    • [associatedDataContent](#associated-data-content)
    • +
    • [associatedDataContentAll](#associated-data-content-all)
    • +
    • [dataInLocales](#data-in-locales)
    • +
    • [dataInLocalesAll](#data-in-locales-all)
    • +
    • [hierarchyContent](#hierarchy-content)
    • +
    • [priceContent](#price-content)
    • +
    • [priceContentAll](#price-content-all)
    • +
    • [priceContentRespectingFilter](#price-content-respecting-filter)
    • +
    • [referenceContent](#reference-content)
    • +
    • [referenceContentAll](#reference-content-all)
    • +
    • [referenceContentWithAttributes](#reference-content-with-attributes)
    • +
    • [referenceContentAllWithAttributes](#reference-content-all-with-attributes)
    • +
    +
    +
    + +Same as the [`entityFetch`](#entity-fetch) but used for fetching entities that represents reference group. + +
    + ## Entity content @@ -540,7 +593,7 @@ dataInLocales(
    argument:string+
    a mandatory specification of the one or more [locales](https://en.wikipedia.org/wiki/IETF_language_tag) in which - the localized entity or reference localized attributes and entity associated data will be fetched; examples of + the localized entity or reference localized attributes and entity associated data will be fetched; examples of a valid language tags are: `en-US` or `en-GB`, `cs` or `cs-CZ`, `de` or `de-AT`, `de-CH`, `fr` or `fr-CA` etc.
@@ -690,8 +743,8 @@ hierarchyContent(
requireConstraint:(entityFetch|stopAt)*
- optional one or more constraints that allow you to define the completeness of the hierarchy entities and - the scope of the traversed hierarchy tree; + optional one or more constraints that allow you to define the completeness of the hierarchy entities and + the scope of the traversed hierarchy tree; any or both of the constraints may be present:
  • [entityFetch](fetching.md#entity-fetch)
  • @@ -864,14 +917,14 @@ priceContent( optional argument of type evita_query/src/main/java/io/evitadb/api/query/require/PriceContentMode.javaEvitaDB.Client/Queries/Requires/PriceContentMode.cs enum allowing you to specify whether to fetch all, selected or no price records for the entity: - - **NONE**: no prices will be fetched for the entity (even if the filter contains a price constraint) + - **NONE**: no prices will be fetched for the entity (even if the filter contains a price constraint) - **RESPECTING_FILTER**: only a prices in price lists selected by a filter constraint will be fetched - **ALL**: all prices of the entity will be fetched (regardless of the price constraint in a filter)
argument:string*
- optional one or more string arguments representing price list names to add to the list of price lists passed in + optional one or more string arguments representing price list names to add to the list of price lists passed in a filter price constraint, which together form a set of price lists for which to fetch prices for the entity
@@ -879,9 +932,9 @@ priceContent( The `priceContent` (evita_query/src/main/java/io/evitadb/api/query/require/PriceContent.javaEvitaDB.Client/Queries/Requires/PriceContent.cs) requirement allows you to access the information about the prices of the entity. -If the `RESPECTING_FILTER` mode is used, the `priceContent` requirement will only retrieve the prices selected by -the [`priceInPriceLists`](../filtering/price.md#price-in-price-lists) constraint. If the enum `NONE` is specified, no -prices are returned at all, if the enum `ALL` is specified, all prices of the entity are returned regardless of the +If the `RESPECTING_FILTER` mode is used, the `priceContent` requirement will only retrieve the prices selected by +the [`priceInPriceLists`](../filtering/price.md#price-in-price-lists) constraint. If the enum `NONE` is specified, no +prices are returned at all, if the enum `ALL` is specified, all prices of the entity are returned regardless of the `priceInPriceLists` constraint in the filter (the constraint still controls whether the entity is returned at all). You can also add additional price lists to the list of price lists passed in the `priceInPriceLists` constraint by @@ -916,7 +969,7 @@ The query returns the following list of prices of the `Product` entity: -As you can see, the prices for the filtered price lists *employee-basic-price* and *basic* are returned. This query is +As you can see, the prices for the filtered price lists *employee-basic-price* and *basic* are returned. This query is equivalent to using the [`priceContentRespectingFilter`](#price-content-respecting-filter) alias. @@ -924,7 +977,7 @@ equivalent to using the [`priceContentRespectingFilter`](#price-content-respecti ### Price content respecting filter ```evitaql-syntax -priceContent( +priceContent( argument:string* ) ``` @@ -932,7 +985,7 @@ priceContent(
argument:string*
- optional one or more string arguments representing price list names to add to the list of price lists passed in + optional one or more string arguments representing price list names to add to the list of price lists passed in a filter price constraint, which together form a set of price lists for which to fetch prices for the entity
@@ -1019,7 +1072,7 @@ The query returns the following list of prices of the `Product` entity: -As you can see, all prices of the entity are returned in all available currencies - not only the filtered price lists +As you can see, all prices of the entity are returned in all available currencies - not only the filtered price lists *employee-basic-price* and *basic*. Thanks to `priceContentAll` you have an overview of all prices of the entity. @@ -1033,16 +1086,16 @@ Each has a different purpose and returns different prices. The price object returns various data that can be formatted by the server for you to display to the user. Specifically, the actual price numbers within the price object can be retrieved formatted according to the specified locale and can even include the currency symbol. This is controlled by the `formatted` and `withCurrency` arguments on the respective price object fields. -The locale is either resolved from the context of the query +The locale is either resolved from the context of the query (either from a localized unique attribute in the filter or from the `entityLocaleEquals` constraint in the filter) or can be specified directly on the parent price field by the `locale` argument. ### Price for sale -The `priceForSale` field returns a single price object representing the price for sale of the entity. -By default, this price is [computed based on input filter constraints](../filtering/price.md), more specifically: +The `priceForSale` field returns a single price object representing the price for sale of the entity. +By default, this price is [computed based on input filter constraints](../filtering/price.md), more specifically: `priceInPriceLists`, `priceInCurrency` and `priceValidIn`. This is expected to be the most common use case, as -it also filters returned entities by these conditions. +it also filters returned entities by these conditions. @@ -1065,7 +1118,7 @@ As you can see, the price for sale matching the filter constraints is returned. Alternatively, if you don't want to filter entities by the price filter constraints, but you still want to compute and fetch -specific price for sale, you can specify which price for sale you want to compute by using the `priceList`, `currency` and +specific price for sale, you can specify which price for sale you want to compute by using the `priceList`, `currency` and `validIn`/`validNow` arguments directly on the `priceForSale` field. You can even combine these two approaches, in which case the arguments on the `priceForSale` fields simply override the corresponding price constraints used in the filter. Theoretically, you can then filter entities by different price conditions than you use to compute the returned price for sale. @@ -1094,13 +1147,13 @@ As you can see, the price for sale matching the custom arguments is returned. The `price` field returns a single specific price (even non-sellable one) based on the arguments passed: `priceList` and `currency`. This is useful, for example, if you want to fetch a reference non-sellable price to display next to the entity's main price for sale. -If more than one price is found for the specified price list and currency, the first valid one is returned +If more than one price is found for the specified price list and currency, the first valid one is returned (the validity is compared either to the current date time or against the `priceValidIn` filter constraint, if present). The `currency` field can be omitted if there is a `priceInCurrency` constraint in the filter, but the `priceList` argument is required. The idea behind this is that you probably would want to use the same currency for all prices in the result, but you probably don't want the reference price to be in the same price list as the main price for sale price, because that would most likely -return the same price. +return the same price. @@ -1124,7 +1177,7 @@ As you can see, the price for sale as well as custom reference price are returne ### Prices -The `prices` field returns all prices of the entity. Both sellable and non-sellable. +The `prices` field returns all prices of the entity. Both sellable and non-sellable. @@ -1147,7 +1200,7 @@ As you can see, the price list is returned. However, if you only need a specific list of prices, you can filter the returned prices with `priceLists` and `currency` arguments. -Unlike the other price fields, this field doesn't fall back to data from filter constraints because that would make it +Unlike the other price fields, this field doesn't fall back to data from filter constraints because that would make it difficult to return all the prices. @@ -1177,7 +1230,7 @@ As you can see, the filtered price list is returned. ```evitaql-syntax -referenceContent( +referenceContent( argument:string+, filterConstraint:any, orderConstraint:any, @@ -1190,38 +1243,38 @@ referenceContent(
argument:string+
mandatory one or more string arguments representing the names of the references to fetch for the entity; - if more than one name is given in the argument, any corresponding constraints in the same `referenceContent` + if more than one name is given in the argument, any corresponding constraints in the same `referenceContent` container will apply to all of them
filterConstraint:any
- optional filter constraint that allows you to filter the references to be fetched for the entity; + optional filter constraint that allows you to filter the references to be fetched for the entity; the filter constraint is targeted at the reference attributes, so if you want to filter by properties of the referenced entity, you must use the [`entityHaving`](../filtering/references.md#entity-having) constraint
orderConstraint:any
- optional ordering constraint that allows you to sort the fetched references; the ordering constraint is targeted + optional ordering constraint that allows you to sort the fetched references; the ordering constraint is targeted at the reference attributes, so if you want to order by properties of the referenced entity, you must use the [`entityProperty`](../ordering/references.md#entity-property) constraint
requireConstraint:entityFetch
- optional requirement constraint that allows you to fetch the referenced entity body; the `entityFetch` - constraint can contain nested `referenceContent` with an additional `entityFetch` / `entityGroupFetch` + optional requirement constraint that allows you to fetch the referenced entity body; the `entityFetch` + constraint can contain nested `referenceContent` with an additional `entityFetch` / `entityGroupFetch` constraints that allows you to fetch the entities in a graph-like manner to an "infinite" depth
requireConstraint:entityGroupFetch
- optional requirement constraint that allows you to fetch the referenced entity group body; the `entityGroupFetch` - constraint can contain nested `referenceContent` with an additional `entityFetch` / `entityGroupFetch` + optional requirement constraint that allows you to fetch the referenced entity group body; the `entityGroupFetch` + constraint can contain nested `referenceContent` with an additional `entityFetch` / `entityGroupFetch` constraints that allows you to fetch the entities in a graph-like manner to an "infinite" depth
The `referenceContent` (evita_query/src/main/java/io/evitadb/api/query/require/ReferenceContent.javaEvitaDB.Client/Queries/Requires/ReferenceContent.cs) requirement allows you to access the information about the references the entity has towards other entities (either -managed by evitaDB itself or by any other external system). This variant of `referenceContent` doesn't return +managed by evitaDB itself or by any other external system). This variant of `referenceContent` doesn't return the attributes set on the reference itself - if you need those attributes, use the [`referenceContentWithAttributes`](#reference-content-with-attributes) variant of it. @@ -1272,7 +1325,7 @@ The returned `Product` entity will contain primary keys of all categories and br #### Referenced entity (group) fetching -In many scenarios, you'll need to fetch not only the primary keys of the referenced entities, but also their bodies and +In many scenarios, you'll need to fetch not only the primary keys of the referenced entities, but also their bodies and the bodies of the groups the references refer to. One such common scenario is fetching the parameters of a product: @@ -1287,7 +1340,7 @@ the bodies of the groups the references refer to. One such common scenario is fe ##### The result of an entity fetched with referenced parameter bodies and group bodies -The returned `Product` entity will contain a list of all parameter codes it references and the code of the group to +The returned `Product` entity will contain a list of all parameter codes it references and the code of the group to which each parameter belongs: @@ -1306,13 +1359,13 @@ which each parameter belongs: -The example lists only a *code* attribute for each referenced entity and group for brevity, but you can retrieve any of +The example lists only a *code* attribute for each referenced entity and group for brevity, but you can retrieve any of their content - associated data, prices, hierarchies, or nested references as well. -To demonstrate graph-like fetching of multiple referenced levels, let's fetch a product with its group assignment and -for each group fetch the group's tags and for each tag fetch the tag's category name. The query contains 4 levels of +To demonstrate graph-like fetching of multiple referenced levels, let's fetch a product with its group assignment and +for each group fetch the group's tags and for each tag fetch the tag's category name. The query contains 4 levels of related entities: product → group → tag → tag category. The query looks like this: @@ -1346,7 +1399,7 @@ for each tag its category assignment:
-The tag category is not an entity managed by evitaDB and that's why we retrieve only its primary key. +The tag category is not an entity managed by evitaDB and that's why we retrieve only its primary key. @@ -1354,7 +1407,7 @@ The tag category is not an entity managed by evitaDB and that's why we retrieve #### Reference attributes fetching -Besides fetching the referenced entity bodies, you can also fetch the attributes set on the reference itself. Attributes +Besides fetching the referenced entity bodies, you can also fetch the attributes set on the reference itself. Attributes of each reference can be fetched using `attributes` field where all possible attributes of particular reference are available, similarly to entity attributes. @@ -1391,22 +1444,22 @@ values define the product variant, while the other parameters only describe the #### Filtering references -Sometimes your entities have a lot of references and you don't need all of them in certain scenarios. In this case, you +Sometimes your entities have a lot of references and you don't need all of them in certain scenarios. In this case, you can use the filter constraint to filter out the references you don't need. -The `referenceContent` filter +The `referenceContent` filter on reference fields implicitly targets the attributes on the same reference it points to, so you don't need to -specify a [`referenceHaving`](../filtering/references.md#reference-having) constraint. However, if you need to declare -constraints on referenced entity attributes, you must wrap them in the [`entityHaving`](../filtering/references.md#entity-having) +specify a [`referenceHaving`](../filtering/references.md#reference-having) constraint. However, if you need to declare +constraints on referenced entity attributes, you must wrap them in the [`entityHaving`](../filtering/references.md#entity-having) container constraint. -For example, your product has got a lot of parameters, but on product detail page you need to fetch only those that are -part of group which contains an attribute *isVisibleInDetail* set to *TRUE*.To fetch only those parameters, use the +For example, your product has got a lot of parameters, but on product detail page you need to fetch only those that are +part of group which contains an attribute *isVisibleInDetail* set to *TRUE*.To fetch only those parameters, use the following query: @@ -1446,22 +1499,22 @@ As you can see only the parameters of the groups having *isVisibleInDetail* set #### Ordering references -By default, the references are ordered by the primary key of the referenced entity. If you want to order the references -by a different property - either the attribute set on the reference itself or the property of the referenced entity - +By default, the references are ordered by the primary key of the referenced entity. If you want to order the references +by a different property - either the attribute set on the reference itself or the property of the referenced entity - you can use the order constraint inside the `referenceContent` requirement. The `referenceContent` ordering -on reference fields implicitly targets the attributes on the same reference -it points to, so you don't need to specify a [`referenceProperty`](../ordering/reference.md#reference-property) constraint. -However, if you need to declare -constraints on referenced entity attributes, you must wrap them in the [`entityProperty`](../ordering/reference.md#entity-property) +on reference fields implicitly targets the attributes on the same reference +it points to, so you don't need to specify a [`referenceProperty`](../ordering/reference.md#reference-property) constraint. +However, if you need to declare +constraints on referenced entity attributes, you must wrap them in the [`entityProperty`](../ordering/reference.md#entity-property) container constraint. -Let's say you want your parameters to be ordered by an English name of the parameter. To do this, use the following +Let's say you want your parameters to be ordered by an English name of the parameter. To do this, use the following query: @@ -1501,7 +1554,7 @@ The returned `Product` entity will contain a list of all parameters in the expec ### Reference content all ```evitaql-syntax -referenceContentAll( +referenceContentAll( filterConstraint:any, orderConstraint:any, requireConstraint:entityFetch, @@ -1512,26 +1565,26 @@ referenceContentAll(
filterConstraint:any
- optional filter constraint that allows you to filter the references to be fetched for the entity; + optional filter constraint that allows you to filter the references to be fetched for the entity; the filter constraint is targeted at the reference attributes, so if you want to filter by properties of the referenced entity, you must use the [`entityHaving`](../filtering/references.md#entity-having) constraint
orderConstraint:any
- optional ordering constraint that allows you to sort the fetched references; the ordering constraint is targeted + optional ordering constraint that allows you to sort the fetched references; the ordering constraint is targeted at the reference attributes, so if you want to order by properties of the referenced entity, you must use the [`entityProperty`](../ordering/references.md#entity-property) constraint
requireConstraint:entityFetch
- optional requirement constraint that allows you to fetch the referenced entity body; the `entityFetch` - constraint can contain nested `referenceContent` with an additional `entityFetch` / `entityGroupFetch` + optional requirement constraint that allows you to fetch the referenced entity body; the `entityFetch` + constraint can contain nested `referenceContent` with an additional `entityFetch` / `entityGroupFetch` constraints that allows you to fetch the entities in a graph-like manner to an "infinite" depth
requireConstraint:entityGroupFetch
- optional requirement constraint that allows you to fetch the referenced entity group body; the `entityGroupFetch` - constraint can contain nested `referenceContent` with an additional `entityFetch` / `entityGroupFetch` + optional requirement constraint that allows you to fetch the referenced entity group body; the `entityGroupFetch` + constraint can contain nested `referenceContent` with an additional `entityFetch` / `entityGroupFetch` constraints that allows you to fetch the entities in a graph-like manner to an "infinite" depth
@@ -1575,7 +1628,7 @@ The returned `Product` entity will contain primary keys and codes of all its ref ### Reference content with attributes ```evitaql-syntax -referenceContentWithAttributes( +referenceContentWithAttributes( argument:string+, filterConstraint:any, orderConstraint:any, @@ -1592,13 +1645,13 @@ referenceContentWithAttributes(
filterConstraint:any
- optional filter constraint that allows you to filter the references to be fetched for the entity; + optional filter constraint that allows you to filter the references to be fetched for the entity; the filter constraint is targeted at the reference attributes, so if you want to filter by properties of the referenced entity, you must use the [`entityHaving`](../filtering/references.md#entity-having) constraint
orderConstraint:any
- optional ordering constraint that allows you to sort the fetched references; the ordering constraint is targeted + optional ordering constraint that allows you to sort the fetched references; the ordering constraint is targeted at the reference attributes, so if you want to order by properties of the referenced entity, you must use the [`entityProperty`](../ordering/references.md#entity-property) constraint
@@ -1609,14 +1662,14 @@ referenceContentWithAttributes(
requireConstraint:entityFetch
- optional requirement constraint that allows you to fetch the referenced entity body; the `entityFetch` - constraint can contain nested `referenceContent` with an additional `entityFetch` / `entityGroupFetch` + optional requirement constraint that allows you to fetch the referenced entity body; the `entityFetch` + constraint can contain nested `referenceContent` with an additional `entityFetch` / `entityGroupFetch` constraints that allows you to fetch the entities in a graph-like manner to an "infinite" depth
requireConstraint:entityGroupFetch
- optional requirement constraint that allows you to fetch the referenced entity group body; the `entityGroupFetch` - constraint can contain nested `referenceContent` with an additional `entityFetch` / `entityGroupFetch` + optional requirement constraint that allows you to fetch the referenced entity group body; the `entityGroupFetch` + constraint can contain nested `referenceContent` with an additional `entityFetch` / `entityGroupFetch` constraints that allows you to fetch the entities in a graph-like manner to an "infinite" depth
@@ -1624,12 +1677,12 @@ referenceContentWithAttributes( The `referenceContentWithAttributes` (evita_query/src/main/java/io/evitadb/api/query/require/ReferenceContent.javaEvitaDB.Client/Queries/Requires/ReferenceContent.cs) is a variation of the [`referenceContent`](#reference-content) requirement that allows you to access the information about the references the entity has towards other entities (either managed by evitaDB itself or by any other external -system) and the attributes set on those references. The `referenceContentWithAttributes` allows you to specify the list +system) and the attributes set on those references. The `referenceContentWithAttributes` allows you to specify the list of attributes to fetch, but by default it fetches all attributes on the reference. For detail information, see the [`referenceContent`](#reference-content) requirement chapter. -To obtain an entity with reference to a parameter value that reveals which association defines the unique product-variant +To obtain an entity with reference to a parameter value that reveals which association defines the unique product-variant combination and which parameter values are merely informative, use the following query: @@ -1658,7 +1711,7 @@ of the relation between the product and the parameter value: -As you can see, the *cellular-true*, *display-size-10-2*, *ram-memory-4*, *rom-memory-256* and *color-yellow* parameter +As you can see, the *cellular-true*, *display-size-10-2*, *ram-memory-4*, *rom-memory-256* and *color-yellow* parameter values define the product variant, while the other parameters only describe the additional properties of the product. @@ -1670,7 +1723,7 @@ values define the product variant, while the other parameters only describe the ### Reference content all with attributes ```evitaql-syntax -referenceContentAllWithAttributes( +referenceContentAllWithAttributes( filterConstraint:any, orderConstraint:any, requireConstraint:attributeContent, @@ -1682,13 +1735,13 @@ referenceContentAllWithAttributes(
filterConstraint:any
- optional filter constraint that allows you to filter the references to be fetched for the entity; + optional filter constraint that allows you to filter the references to be fetched for the entity; the filter constraint is targeted at the reference attributes, so if you want to filter by properties of the referenced entity, you must use the [`entityHaving`](../filtering/references.md#entity-having) constraint
orderConstraint:any
- optional ordering constraint that allows you to sort the fetched references; the ordering constraint is targeted + optional ordering constraint that allows you to sort the fetched references; the ordering constraint is targeted at the reference attributes, so if you want to order by properties of the referenced entity, you must use the [`entityProperty`](../ordering/references.md#entity-property) constraint
@@ -1699,14 +1752,14 @@ referenceContentAllWithAttributes(
requireConstraint:entityFetch
- optional requirement constraint that allows you to fetch the referenced entity body; the `entityFetch` - constraint can contain nested `referenceContent` with an additional `entityFetch` / `entityGroupFetch` + optional requirement constraint that allows you to fetch the referenced entity body; the `entityFetch` + constraint can contain nested `referenceContent` with an additional `entityFetch` / `entityGroupFetch` constraints that allows you to fetch the entities in a graph-like manner to an "infinite" depth
requireConstraint:entityGroupFetch
- optional requirement constraint that allows you to fetch the referenced entity group body; the `entityGroupFetch` - constraint can contain nested `referenceContent` with an additional `entityFetch` / `entityGroupFetch` + optional requirement constraint that allows you to fetch the referenced entity group body; the `entityGroupFetch` + constraint can contain nested `referenceContent` with an additional `entityFetch` / `entityGroupFetch` constraints that allows you to fetch the entities in a graph-like manner to an "infinite" depth
@@ -1715,7 +1768,7 @@ The `referenceContentAllWithAttributes` ( constraintClass, - @Nonnull ConstraintType type, - @Nonnull ConstraintPropertyType propertyType, - @Nonnull String fullName, - @Nonnull String shortDescription, - @Nonnull Set supportedIn, - @Nullable SupportedValues supportedValues, - @Nonnull ConstraintCreator creator) implements Comparable { +public class ConstraintDescriptor implements Comparable { private static final Pattern NAME_PATTERN = Pattern.compile("^[a-z][a-zA-Z]*$"); + @Nonnull private final Class constraintClass; + @Nonnull private final ConstraintType type; + @Nonnull private final ConstraintPropertyType propertyType; + @Nonnull private final String fullName; + @Nonnull private final String shortDescription; + @Nonnull private final String userDocsLink; + @Nonnull private final Set supportedIn; + @Nullable private final SupportedValues supportedValues; + @Nonnull private final ConstraintCreator creator; + public ConstraintDescriptor(@Nonnull Class constraintClass, @Nonnull ConstraintType type, @Nonnull ConstraintPropertyType propertyType, @Nonnull String fullName, @Nonnull String shortDescription, + @Nonnull String userDocsLink, @Nonnull Set supportedIn, @Nullable SupportedValues supportedValues, @Nonnull ConstraintCreator creator) { @@ -86,6 +82,7 @@ public ConstraintDescriptor(@Nonnull Class constraintClass, this.propertyType = propertyType; this.fullName = fullName; this.shortDescription = shortDescription; + this.userDocsLink = constructFullUserDocsLink(userDocsLink); this.supportedIn = supportedIn; this.supportedValues = supportedValues; this.creator = creator; @@ -98,6 +95,10 @@ public ConstraintDescriptor(@Nonnull Class constraintClass, !this.shortDescription.isEmpty(), "Constraint `" + fullName + "` is missing short description." ); + Assert.isPremiseValid( + !userDocsLink.isEmpty() && userDocsLink.startsWith("/"), + "Constraint `" + fullName + "` is missing user documentation link or the link has incorrect format." + ); if (propertyType == ConstraintPropertyType.GENERIC) { Assert.isPremiseValid( @@ -107,6 +108,76 @@ public ConstraintDescriptor(@Nonnull Class constraintClass, } } + /** + * Class of represented constraint. + */ + @Nonnull + public Class constraintClass() { + return constraintClass; + } + + /** + * Specifies what is purpose of the constraint + */ + @Nonnull + public ConstraintType type() { + return type; + } + + /** + * Specifies on which data the constraint will be operating. + */ + @Nonnull + public ConstraintPropertyType propertyType() { + return propertyType; + } + + /** + * Base name of condition or operation this constraint represent, e.g. `equals` + creator suffix. + * Its format must be in camelCase. + */ + @Nonnull + public String fullName() { + return fullName; + } + + @Nonnull + public String shortDescription() { + return shortDescription; + } + + /** + * Full link to user documentation of this constraint on evitadb.io web. + */ + @Nonnull + public String userDocsLink() { + return userDocsLink; + } + + /** + * Set of domains in which this constraint is supported in and can be used in when querying. + */ + @Nonnull + public Set supportedIn() { + return supportedIn; + } + + /** + * Description of target data this constraint can operate on. If null, query doesn't support any values + */ + @Nullable + public SupportedValues supportedValues() { + return supportedValues; + } + + /** + * Contains data for reconstructing original constraints. + */ + @Nonnull + public ConstraintCreator creator() { + return creator; + } + @Override public boolean equals(Object o) { // lombok cannot generate equals and hash code for records @@ -157,6 +228,11 @@ public int compareTo(@Nonnull ConstraintDescriptor o) { return result; } + @Nonnull + private String constructFullUserDocsLink(@Nonnull String relativeUserDocsLink) { + return "https://evitadb.io" + relativeUserDocsLink; + } + /** * Contains metadata about supported data types of target values this query can operate on. diff --git a/evita_query/src/main/java/io/evitadb/api/query/descriptor/ConstraintProcessor.java b/evita_query/src/main/java/io/evitadb/api/query/descriptor/ConstraintProcessor.java index 6142c5a97..48bd0ac3f 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/descriptor/ConstraintProcessor.java +++ b/evita_query/src/main/java/io/evitadb/api/query/descriptor/ConstraintProcessor.java @@ -95,6 +95,7 @@ Set process(@Nonnull Set>> c ? constraintDefinition.name() : constraintDefinition.name() + StringUtils.capitalize(creator.suffix().get()), constraintDefinition.shortDescription(), + constraintDefinition.userDocsLink(), Set.of(constraintDefinition.supportedIn()), supportedValues, creator diff --git a/evita_query/src/main/java/io/evitadb/api/query/descriptor/annotation/ConstraintDefinition.java b/evita_query/src/main/java/io/evitadb/api/query/descriptor/annotation/ConstraintDefinition.java index 793193ef6..2c486106d 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/descriptor/annotation/ConstraintDefinition.java +++ b/evita_query/src/main/java/io/evitadb/api/query/descriptor/annotation/ConstraintDefinition.java @@ -33,10 +33,10 @@ import java.lang.annotation.Target; /** - * Constraint definition that describes concrete query. This definition when processed can be later used when + * Constraint definition that describes concrete constraint. This definition when processed can be later used when * building some system based on these constraints (e.g. custom query language that can be translated to this original). *

- * Such an annotated query must have also specified creator constructor using {@link Creator} and + * Such an annotated constraint must have also specified creator constructor using {@link Creator} and * its parameters with {@link Classifier}, {@link Value} and {@link Child}. *

* This data is then processed by {@link ConstraintDescriptorProvider}. @@ -49,10 +49,10 @@ public @interface ConstraintDefinition { /** - * Base short name of query in defined type and property type categorization. Should specify name of condition, - * operation or something like that, that this query represent (e.g. "equals", "fetch"). + * Base short name of constraint in defined type and property type categorization. Should specify name of condition, + * operation or something like that, that this constraint represent (e.g. "equals", "fetch"). * Its format must be in camelCase and may be suffixed in concrete creators with their suffixes, making full name of - * query. + * constraint. */ String name(); @@ -62,7 +62,13 @@ String shortDescription(); /** - * Set of domains in which this query is supported in and can be used in when querying. + * Relative link to user documentation where this constraint is described in more detail. + * E.g., "/documentation/query/filtering/price#price-in-price-lists" for constraint "PriceInPriceLists". + */ + String userDocsLink(); + + /** + * Set of domains in which this constraint is supported in and can be used in when querying. * Default is {@link ConstraintDomain#ENTITY}. */ ConstraintDomain[] supportedIn() default { ConstraintDomain.GENERIC }; diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/And.java b/evita_query/src/main/java/io/evitadb/api/query/filter/And.java index e07087e4a..5d6a9331c 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/And.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/And.java @@ -93,6 +93,7 @@ @ConstraintDefinition( name = "and", shortDescription = "The container that combines inner constraints with [logical AND](https://en.wikipedia.org/wiki/Logical_conjunction).", + userDocsLink = "/documentation/query/filtering/logical#and", supportedIn = { ConstraintDomain.ENTITY, ConstraintDomain.REFERENCE } ) public class And extends AbstractFilterConstraintContainer implements GenericConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeBetween.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeBetween.java index c1e5076e1..85cf1a51d 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeBetween.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeBetween.java @@ -86,6 +86,7 @@ @ConstraintDefinition( name = "between", shortDescription = "Compares value of the attribute with passed value and checks if the value of that attribute is within the passed range (both ends are inclusive).", + userDocsLink = "/documentation/query/filtering/comparable#attribute-between", supportedIn = { ConstraintDomain.ENTITY, ConstraintDomain.REFERENCE }, supportedValues = @ConstraintSupportedValues(allTypesSupported = true, arraysSupported = true) ) diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeContains.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeContains.java index c47128fbe..f29743f31 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeContains.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeContains.java @@ -61,6 +61,7 @@ @ConstraintDefinition( name = "contains", shortDescription = "Compares value of the attribute with passed value and checks if the text value of that attribute contains part of passed text (case-sensitive).", + userDocsLink = "/documentation/query/filtering/string#attribute-contains", supportedIn = {ConstraintDomain.ENTITY, ConstraintDomain.REFERENCE}, supportedValues = @ConstraintSupportedValues(supportedTypes = String.class, arraysSupported = true) ) diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeEndsWith.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeEndsWith.java index 7cbc6fe7b..6907e9d37 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeEndsWith.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeEndsWith.java @@ -62,6 +62,7 @@ @ConstraintDefinition( name = "endsWith", shortDescription = "Compares value of the attribute with passed value and checks if the text value of that attribute ends with passed text (case-sensitive).", + userDocsLink = "/documentation/query/filtering/string#attribute-ends-with", supportedIn = { ConstraintDomain.ENTITY, ConstraintDomain.REFERENCE }, supportedValues = @ConstraintSupportedValues(supportedTypes = String.class, arraysSupported = true) ) diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeEquals.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeEquals.java index e1aa0b8c9..e9860edb8 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeEquals.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeEquals.java @@ -63,6 +63,7 @@ @ConstraintDefinition( name = "equals", shortDescription = "Compares value of the attribute with passed value and checks if they are both equal.", + userDocsLink = "/documentation/query/filtering/comparable#attribute-equals", supportedIn = { ConstraintDomain.ENTITY, ConstraintDomain.REFERENCE }, supportedValues = @ConstraintSupportedValues(allTypesSupported = true, arraysSupported = true) ) diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeGreaterThan.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeGreaterThan.java index 6a2e226ff..86ca78c72 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeGreaterThan.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeGreaterThan.java @@ -57,6 +57,7 @@ @ConstraintDefinition( name = "greaterThan", shortDescription = "Compares value of the attribute with passed value and checks if the value of that attribute is greater than the passed value.", + userDocsLink = "/documentation/query/filtering/comparable#attribute-greater-than", supportedIn = { ConstraintDomain.ENTITY, ConstraintDomain.REFERENCE }, supportedValues = @ConstraintSupportedValues(allTypesSupported = true, arraysSupported = true) ) diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeGreaterThanEquals.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeGreaterThanEquals.java index d38bc1fc5..3ccc908fc 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeGreaterThanEquals.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeGreaterThanEquals.java @@ -58,6 +58,7 @@ @ConstraintDefinition( name = "greaterThanEquals", shortDescription = "Compares value of the attribute with passed value and checks if the value of that attribute is greater than or equals to the passed value.", + userDocsLink = "/documentation/query/filtering/comparable#attribute-greater-than-equals", supportedIn = { ConstraintDomain.ENTITY, ConstraintDomain.REFERENCE }, supportedValues = @ConstraintSupportedValues(allTypesSupported = true, arraysSupported = true) ) diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeInRange.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeInRange.java index 9a1e25bb5..3858c45e4 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeInRange.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeInRange.java @@ -80,6 +80,7 @@ shortDescription = "Compares value of the attribute with passed value and checks if the range value of that " + "attribute contains the passed value within its limits (both ends are inclusive). " + "The constraint can be used only for Range data type values.", + userDocsLink = "/documentation/query/filtering/range#attribute-in-range", supportedIn = { ConstraintDomain.ENTITY, ConstraintDomain.REFERENCE }, supportedValues = @ConstraintSupportedValues( supportedTypes = { diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeInSet.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeInSet.java index 070d5b943..2619a80a1 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeInSet.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeInSet.java @@ -67,6 +67,7 @@ shortDescription = "Compares value of the attribute with passed value and checks if the value of that attribute " + "equals to at least one of the passed values. " + "The constraint is equivalent to the multiple `equals` constraints combined with logical OR.", + userDocsLink = "/documentation/query/filtering/comparable#attribute-in-set", supportedIn = {ConstraintDomain.ENTITY, ConstraintDomain.REFERENCE}, supportedValues = @ConstraintSupportedValues(allTypesSupported = true, arraysSupported = true) ) diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeIs.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeIs.java index 5d962c490..de8563437 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeIs.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeIs.java @@ -58,6 +58,7 @@ @ConstraintDefinition( name = "is", shortDescription = "The constraint if value of the attribute is same as passed special value.", + userDocsLink = "/documentation/query/filtering/comparable#attribute-is", supportedIn = { ConstraintDomain.ENTITY, ConstraintDomain.REFERENCE }, supportedValues = @ConstraintSupportedValues(allTypesSupported = true, arraysSupported = true) ) diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeLessThan.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeLessThan.java index 1579a3008..a2765b0d9 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeLessThan.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeLessThan.java @@ -57,6 +57,7 @@ @ConstraintDefinition( name = "lessThan", shortDescription = "Compares value of the attribute with passed value and checks if the value of that attribute is less than the passed value.", + userDocsLink = "/documentation/query/filtering/comparable#attribute-less-than", supportedIn = { ConstraintDomain.ENTITY, ConstraintDomain.REFERENCE }, supportedValues = @ConstraintSupportedValues(allTypesSupported = true, arraysSupported = true) ) diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeLessThanEquals.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeLessThanEquals.java index b95442a05..4295b9b3c 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeLessThanEquals.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeLessThanEquals.java @@ -58,6 +58,7 @@ @ConstraintDefinition( name = "lessThanEquals", shortDescription = "Compares value of the attribute with passed value and checks if the value of that attribute is less than or equals to the passed value.", + userDocsLink = "/documentation/query/filtering/comparable#attribute-less-than-equals", supportedIn = { ConstraintDomain.ENTITY, ConstraintDomain.REFERENCE }, supportedValues = @ConstraintSupportedValues(allTypesSupported = true, arraysSupported = true) ) diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeStartsWith.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeStartsWith.java index 81e3a1ff7..41f0a1d42 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeStartsWith.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeStartsWith.java @@ -62,6 +62,7 @@ @ConstraintDefinition( name = "startsWith", shortDescription = "Compares value of the attribute with passed value and checks if the text value of that attributes starts with passed text. (case-sensitive)", + userDocsLink = "/documentation/query/filtering/string#attribute-starts-with", supportedIn = { ConstraintDomain.ENTITY, ConstraintDomain.REFERENCE }, supportedValues = @ConstraintSupportedValues( supportedTypes = String.class, diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/EntityHaving.java b/evita_query/src/main/java/io/evitadb/api/query/filter/EntityHaving.java index 4d62b11a5..947dd4876 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/EntityHaving.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/EntityHaving.java @@ -59,6 +59,7 @@ shortDescription = "The container allowing to filter entities by having references to entities managed by evitaDB that " + "match inner filtering constraints. This container resembles the SQL inner join clauses where the `entityHaving`" + "contains the filtering condition on particular join.", + userDocsLink = "/documentation/query/filtering/references#entity-having", supportedIn = ConstraintDomain.REFERENCE ) public class EntityHaving extends AbstractFilterConstraintContainer implements EntityConstraint, SeparateEntityScopeContainer { diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/EntityLocaleEquals.java b/evita_query/src/main/java/io/evitadb/api/query/filter/EntityLocaleEquals.java index f5c5d4e4f..f1445c570 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/EntityLocaleEquals.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/EntityLocaleEquals.java @@ -71,6 +71,7 @@ @ConstraintDefinition( name = "equals", shortDescription = "The constraint if at least one of entity locales (derived from entity attributes or associated data) equals to the passed one.", + userDocsLink = "/documentation/query/filtering/locale#entity-locale-equals", supportedIn = ConstraintDomain.ENTITY ) public class EntityLocaleEquals extends AbstractFilterConstraintLeaf diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/EntityPrimaryKeyInSet.java b/evita_query/src/main/java/io/evitadb/api/query/filter/EntityPrimaryKeyInSet.java index b0278df48..d9c0ef3ec 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/EntityPrimaryKeyInSet.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/EntityPrimaryKeyInSet.java @@ -50,6 +50,7 @@ name = "inSet", shortDescription = "The constraint checks if primary key of the entity equals to at least one of the passed values. " + "The constraint is equivalent to one or more `equals` constraints combined with logical OR.", + userDocsLink = "/documentation/query/filtering/constant#entity-primary-key-in-set", supportedIn = { ConstraintDomain.ENTITY, ConstraintDomain.REFERENCE } ) public class EntityPrimaryKeyInSet extends AbstractFilterConstraintLeaf diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/FacetHaving.java b/evita_query/src/main/java/io/evitadb/api/query/filter/FacetHaving.java index fddd25e41..e67948f76 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/FacetHaving.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/FacetHaving.java @@ -64,6 +64,7 @@ shortDescription = "The container allowing to filter entities by having references to entities managed by evitaDB that " + "match the inner filter constraint. This container resembles the SQL inner join clauses and works in cooperation " + "with facet summary requirement.", + userDocsLink = "/documentation/query/filtering/references#facet-having", supportedIn = ConstraintDomain.ENTITY ) public class FacetHaving extends AbstractFilterConstraintContainer implements FacetConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/FilterBy.java b/evita_query/src/main/java/io/evitadb/api/query/filter/FilterBy.java index 93b23cdae..2e7100fbe 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/FilterBy.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/FilterBy.java @@ -57,7 +57,8 @@ */ @ConstraintDefinition( name = "filterBy", - shortDescription = "The container encapsulating inner filter constraint into one main constraint that is required by the query." + shortDescription = "The container encapsulating inner filter constraint into one main constraint that is required by the query.", + userDocsLink = "/documentation/query/basics#filter-by" ) public class FilterBy extends AbstractFilterConstraintContainer implements GenericConstraint { @Serial private static final long serialVersionUID = -2294600717092701351L; diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/FilterGroupBy.java b/evita_query/src/main/java/io/evitadb/api/query/filter/FilterGroupBy.java index 14b436b9b..760beb18b 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/FilterGroupBy.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/FilterGroupBy.java @@ -63,6 +63,7 @@ @ConstraintDefinition( name = "filterGroupBy", shortDescription = "The container encapsulating filter constraint limiting the facet groups returned in facet summary.", + userDocsLink = "/documentation/query/basics#filter-by", supportedIn = ConstraintDomain.REFERENCE ) public class FilterGroupBy extends AbstractFilterConstraintContainer implements GenericConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyDirectRelation.java b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyDirectRelation.java index 6976d0b87..655cdff16 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyDirectRelation.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyDirectRelation.java @@ -91,6 +91,7 @@ @ConstraintDefinition( name = "directRelation", shortDescription = "The constraint limits hierarchy within parent constraint to take only directly related entities into an account.", + userDocsLink = "/documentation/query/filtering/hierarchy#direct-relation", supportedIn = ConstraintDomain.HIERARCHY ) public class HierarchyDirectRelation extends AbstractFilterConstraintLeaf implements HierarchySpecificationFilterConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyExcluding.java b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyExcluding.java index d27519df0..15dd84fba 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyExcluding.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyExcluding.java @@ -126,6 +126,7 @@ @ConstraintDefinition( name = "excluding", shortDescription = "The constraint narrows hierarchy within parent constraint to exclude specified hierarchy subtrees from search.", + userDocsLink = "/documentation/query/filtering/hierarchy#excluding", supportedIn = ConstraintDomain.HIERARCHY ) public class HierarchyExcluding extends AbstractFilterConstraintContainer implements HierarchySpecificationFilterConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyExcludingRoot.java b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyExcludingRoot.java index d77d5e112..f414da35a 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyExcludingRoot.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyExcludingRoot.java @@ -100,6 +100,7 @@ @ConstraintDefinition( name = "excludingRoot", shortDescription = "The constraint limits hierarchy within parent constraint to exclude the entities directly related to the searched root node.", + userDocsLink = "/documentation/query/filtering/hierarchy#excluding-root", supportedIn = ConstraintDomain.HIERARCHY ) public class HierarchyExcludingRoot extends AbstractFilterConstraintLeaf implements HierarchySpecificationFilterConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyHaving.java b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyHaving.java index 04e7d6002..a86bdfa03 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyHaving.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyHaving.java @@ -137,6 +137,7 @@ @ConstraintDefinition( name = "having", shortDescription = "The constraint narrows hierarchy within parent constraint to include specified hierarchy subtrees from search.", + userDocsLink = "/documentation/query/filtering/hierarchy#having", supportedIn = ConstraintDomain.HIERARCHY ) public class HierarchyHaving extends AbstractFilterConstraintContainer implements HierarchySpecificationFilterConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyWithin.java b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyWithin.java index 0a79f6cd2..a25b66f50 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyWithin.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyWithin.java @@ -113,6 +113,7 @@ @ConstraintDefinition( name = "within", shortDescription = "The constraint if entity is placed inside the defined hierarchy tree (or has reference to any hierarchical entity in the tree).", + userDocsLink = "/documentation/query/filtering/hierarchy#hierarchy-within", supportedIn = ConstraintDomain.ENTITY ) public class HierarchyWithin extends AbstractFilterConstraintContainer diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyWithinRoot.java b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyWithinRoot.java index 89b8d999b..3f19c4f19 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyWithinRoot.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyWithinRoot.java @@ -110,6 +110,7 @@ @ConstraintDefinition( name = "withinRoot", shortDescription = "The constraint if entity is placed inside the defined hierarchy tree starting at the root of the tree (or has reference to any hierarchical entity in the tree).", + userDocsLink = "/documentation/query/filtering/hierarchy#hierarchy-within-root", supportedIn = ConstraintDomain.ENTITY ) public class HierarchyWithinRoot extends AbstractFilterConstraintContainer diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/Not.java b/evita_query/src/main/java/io/evitadb/api/query/filter/Not.java index b15dee5b7..77955cdce 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/Not.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/Not.java @@ -92,6 +92,7 @@ @ConstraintDefinition( name = "not", shortDescription = "The container that behaves as [logical NOT](https://en.wikipedia.org/wiki/Negation) for the inner constraint.", + userDocsLink = "/documentation/query/filtering/logical#not", supportedIn = { ConstraintDomain.ENTITY, ConstraintDomain.REFERENCE } ) public class Not extends AbstractFilterConstraintContainer implements GenericConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/Or.java b/evita_query/src/main/java/io/evitadb/api/query/filter/Or.java index fc9cec75e..6b6c1e852 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/Or.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/Or.java @@ -93,6 +93,7 @@ @ConstraintDefinition( name = "or", shortDescription = "The container that combines inner constraints with [logical OR](https://en.wikipedia.org/wiki/Logical_disjunction).", + userDocsLink = "/documentation/query/filtering/logical#or", supportedIn = { ConstraintDomain.ENTITY, ConstraintDomain.REFERENCE } ) public class Or extends AbstractFilterConstraintContainer implements GenericConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/PriceBetween.java b/evita_query/src/main/java/io/evitadb/api/query/filter/PriceBetween.java index 13f508d84..4bcab08e2 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/PriceBetween.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/PriceBetween.java @@ -56,6 +56,7 @@ @ConstraintDefinition( name = "between", shortDescription = "The constraint checks if entity has price for sale within the passed range of prices (both ends are inclusive).", + userDocsLink = "/documentation/query/filtering/price#price-between", supportedIn = ConstraintDomain.ENTITY ) public class PriceBetween extends AbstractFilterConstraintLeaf implements PriceConstraint, IndexUsingConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/PriceInCurrency.java b/evita_query/src/main/java/io/evitadb/api/query/filter/PriceInCurrency.java index 960017bd7..b11e8ac56 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/PriceInCurrency.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/PriceInCurrency.java @@ -52,6 +52,7 @@ @ConstraintDefinition( name = "inCurrency", shortDescription = "The constraint filters out all entities that lack selling price in specified currency.", + userDocsLink = "/documentation/query/filtering/price#price-in-currency", supportedIn = ConstraintDomain.ENTITY ) public class PriceInCurrency extends AbstractFilterConstraintLeaf implements PriceConstraint, IndexUsingConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/PriceInPriceLists.java b/evita_query/src/main/java/io/evitadb/api/query/filter/PriceInPriceLists.java index 500aab785..aa6d02776 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/PriceInPriceLists.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/PriceInPriceLists.java @@ -65,6 +65,7 @@ shortDescription = "The constraint filters out all entities that lack selling price in specified price lists. " + "Order of price lists also defines priority for selecting the entity selling price - the price from first price " + "list in the list will be used as a selling price for the entire entity.", + userDocsLink = "/documentation/query/filtering/price#price-in-price-lists", supportedIn = ConstraintDomain.ENTITY ) public class PriceInPriceLists extends AbstractFilterConstraintLeaf implements PriceConstraint, IndexUsingConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/PriceValidIn.java b/evita_query/src/main/java/io/evitadb/api/query/filter/PriceValidIn.java index 7a9118151..28d896bca 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/PriceValidIn.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/PriceValidIn.java @@ -59,6 +59,7 @@ @ConstraintDefinition( name = "validIn", shortDescription = "The constraint checks if entity has selling price valid at the passed moment.", + userDocsLink = "/documentation/filtering/price#price-valid-in", supportedIn = ConstraintDomain.ENTITY ) public class PriceValidIn extends AbstractFilterConstraintLeaf diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/ReferenceHaving.java b/evita_query/src/main/java/io/evitadb/api/query/filter/ReferenceHaving.java index 8b9d5f77b..df73e52e5 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/ReferenceHaving.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/ReferenceHaving.java @@ -72,6 +72,7 @@ name = "having", shortDescription = "The container allowing to filter entities by having references to entities managed by evitaDB that " + "match the inner filter constraint. This container resembles the SQL inner join clauses.", + userDocsLink = "/documentation/query/filtering/references#reference-having", supportedIn = ConstraintDomain.ENTITY ) public class ReferenceHaving extends AbstractFilterConstraintContainer implements ReferenceConstraint, SeparateEntityScopeContainer { diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/UserFilter.java b/evita_query/src/main/java/io/evitadb/api/query/filter/UserFilter.java index acd542b2b..c76580345 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/UserFilter.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/UserFilter.java @@ -67,6 +67,7 @@ shortDescription = "The container for constraints that are controlled by the user (client UI widgets). " + "It is used mainly to distinguish between user constraint (refining the search) and program defined " + "constraints (considered mandatory), when the extra results are computed.", + userDocsLink = "/documentation/query/filtering/behavioral#user-filter", supportedIn = ConstraintDomain.ENTITY ) public class UserFilter extends AbstractFilterConstraintContainer implements GenericConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/head/Collection.java b/evita_query/src/main/java/io/evitadb/api/query/head/Collection.java index 2893a5352..0bf66f44b 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/head/Collection.java +++ b/evita_query/src/main/java/io/evitadb/api/query/head/Collection.java @@ -50,6 +50,7 @@ @ConstraintDefinition( name = "collection", shortDescription = "The constraint specifies which entity collection will be searched for results.", + userDocsLink = "/documentation/query/basics#header", supportedIn = ConstraintDomain.GENERIC ) public class Collection extends ConstraintLeaf implements HeadConstraint, GenericConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/AttributeNatural.java b/evita_query/src/main/java/io/evitadb/api/query/order/AttributeNatural.java index b2c47946d..78aca411a 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/AttributeNatural.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/AttributeNatural.java @@ -81,6 +81,7 @@ @ConstraintDefinition( name = "natural", shortDescription = "The constraint sorts returned entities by natural ordering of the values in the specified attribute.", + userDocsLink = "/documentation/query/ordering/comparable#attribute-natural", supportedIn = { ConstraintDomain.ENTITY, ConstraintDomain.REFERENCE }, supportedValues = @ConstraintSupportedValues(allTypesSupported = true) ) diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/AttributeSetExact.java b/evita_query/src/main/java/io/evitadb/api/query/order/AttributeSetExact.java index f236ac129..56b16beff 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/AttributeSetExact.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/AttributeSetExact.java @@ -64,6 +64,7 @@ @ConstraintDefinition( name = "setExact", shortDescription = "The constraint sorts returned entities by ordering of the values specified in arguments matching the entity attribute of specified name.", + userDocsLink = "/documentation/query/ordering/constant#exact-entity-attribute-value-order", supportedIn = { ConstraintDomain.ENTITY, ConstraintDomain.REFERENCE }, supportedValues = @ConstraintSupportedValues(allTypesSupported = true) ) diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/AttributeSetInFilter.java b/evita_query/src/main/java/io/evitadb/api/query/order/AttributeSetInFilter.java index a1d82b7ca..3f582a7c9 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/AttributeSetInFilter.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/AttributeSetInFilter.java @@ -65,6 +65,7 @@ @ConstraintDefinition( name = "setInFilter", shortDescription = "The constraint sorts returned entities by ordering of the values specified `attributeInSet` in filter sharing the same attribute name.", + userDocsLink = "/documentation/query/ordering/constant#exact-entity-attribute-value-order-used-in-filter", supportedIn = { ConstraintDomain.ENTITY }, supportedValues = @ConstraintSupportedValues(allTypesSupported = true) ) diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/EntityGroupProperty.java b/evita_query/src/main/java/io/evitadb/api/query/order/EntityGroupProperty.java index 289ada7de..75fba0553 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/EntityGroupProperty.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/EntityGroupProperty.java @@ -110,6 +110,7 @@ @ConstraintDefinition( name = "groupProperty", shortDescription = "The constraint sorts returned references by applying ordering constraint on referenced entity group.", + userDocsLink = "/documentation/query/ordering/reference#entity-group-property", supportedIn = ConstraintDomain.REFERENCE ) public class EntityGroupProperty extends AbstractOrderConstraintContainer implements EntityConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyExact.java b/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyExact.java index 35242739c..88d2cf805 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyExact.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyExact.java @@ -61,6 +61,7 @@ @ConstraintDefinition( name = "exact", shortDescription = "The constraint sorts returned entities by ordering of the values specified in arguments.", + userDocsLink = "/documentation/query/ordering/constant#exact-entity-primary-key-order", supportedIn = { ConstraintDomain.ENTITY, ConstraintDomain.REFERENCE } ) public class EntityPrimaryKeyExact extends AbstractOrderConstraintLeaf implements EntityConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyInFilter.java b/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyInFilter.java index a0940820e..d298d18ab 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyInFilter.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyInFilter.java @@ -63,6 +63,7 @@ @ConstraintDefinition( name = "inFilter", shortDescription = "The constraint sorts returned entities by ordering of the values specified `entityPrimaryKeysInSet` in filter.", + userDocsLink = "/documentation/query/ordering/constant#exact-entity-primary-key-order-used-in-filter", supportedIn = { ConstraintDomain.ENTITY } ) public class EntityPrimaryKeyInFilter extends AbstractOrderConstraintLeaf implements EntityConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyNatural.java b/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyNatural.java index 078f03443..00dbb4c6f 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyNatural.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyNatural.java @@ -55,6 +55,7 @@ @ConstraintDefinition( name = "natural", shortDescription = "The constraint sorts returned entities by primary key in specific order.", + userDocsLink = "/documentation/query/ordering/comparable#primary-key-natural", supportedIn = { ConstraintDomain.ENTITY, ConstraintDomain.REFERENCE } ) public class EntityPrimaryKeyNatural extends AbstractOrderConstraintLeaf implements EntityConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/EntityProperty.java b/evita_query/src/main/java/io/evitadb/api/query/order/EntityProperty.java index fb051e8bb..f017d1854 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/EntityProperty.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/EntityProperty.java @@ -76,6 +76,7 @@ @ConstraintDefinition( name = "property", shortDescription = "The constraint sorts returned references by applying ordering constraint on referenced entity.", + userDocsLink = "/documentation/query/ordering/reference#entity-property", supportedIn = ConstraintDomain.REFERENCE ) public class EntityProperty extends AbstractOrderConstraintContainer implements EntityConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/OrderBy.java b/evita_query/src/main/java/io/evitadb/api/query/order/OrderBy.java index fd93be59b..2dfb13ae2 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/OrderBy.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/OrderBy.java @@ -36,21 +36,21 @@ import java.io.Serializable; /** - * This `orderBy` is container for ordering. It is mandatory container when any ordering is to be used. - * evitaDB requires a previously prepared sort index to be able to sort entities. This fact makes sorting much faster + * This `orderBy` is container for ordering. It is mandatory container when any ordering is to be used. + * evitaDB requires a previously prepared sort index to be able to sort entities. This fact makes sorting much faster * than ad-hoc sorting by attribute value. Also, the sorting mechanism of evitaDB is somewhat different from what you * might be used to. If you sort entities by two attributes in an orderBy clause of the query, evitaDB sorts them first - * by the first attribute (if present) and then by the second (but only those where the first attribute is missing). + * by the first attribute (if present) and then by the second (but only those where the first attribute is missing). * If two entities have the same value of the first attribute, they are not sorted by the second attribute, but by the - * primary key (in ascending order). If we want to use fast "pre-sorted" indexes, there is no other way to do it, + * primary key (in ascending order). If we want to use fast "pre-sorted" indexes, there is no other way to do it, * because the secondary order would not be known until a query time. * - * This default sorting behavior by multiple attributes is not always desirable, so evitaDB allows you to define + * This default sorting behavior by multiple attributes is not always desirable, so evitaDB allows you to define * a sortable attribute compound, which is a virtual attribute composed of the values of several other attributes. * evitaDB also allows you to specify the order of the "pre-sorting" behavior (ascending/descending) for each of these * attributes, and also the behavior for NULL values (first/last) if the attribute is completely missing in the entity. - * The sortable attribute compound is then used in the orderBy clause of the query instead of specifying the multiple - * individual attributes to achieve the expected sorting behavior while maintaining the speed of the "pre-sorted" + * The sortable attribute compound is then used in the orderBy clause of the query instead of specifying the multiple + * individual attributes to achieve the expected sorting behavior while maintaining the speed of the "pre-sorted" * indexes. * * Example: @@ -67,7 +67,8 @@ */ @ConstraintDefinition( name = "orderBy", - shortDescription = "The container encapsulates inner order constraints into one main constraint that is required by the query." + shortDescription = "The container encapsulates inner order constraints into one main constraint that is required by the query.", + userDocsLink = "/documentation/query/basics#order-by" ) public class OrderBy extends AbstractOrderConstraintContainer implements GenericConstraint { @Serial private static final long serialVersionUID = 6352220342769661652L; diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/OrderGroupBy.java b/evita_query/src/main/java/io/evitadb/api/query/order/OrderGroupBy.java index 0dc280b4d..a9c151535 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/OrderGroupBy.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/OrderGroupBy.java @@ -85,6 +85,7 @@ @ConstraintDefinition( name = "orderGroupBy", shortDescription = "The container encapsulates order constraints that control the order of the facet groups in facet summary.", + userDocsLink = "/documentation/query/basics#order-by", supportedIn = ConstraintDomain.REFERENCE ) public class OrderGroupBy extends AbstractOrderConstraintContainer implements GenericConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/PriceNatural.java b/evita_query/src/main/java/io/evitadb/api/query/order/PriceNatural.java index 18bade9a8..bd1185c28 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/PriceNatural.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/PriceNatural.java @@ -55,6 +55,7 @@ @ConstraintDefinition( name = "natural", shortDescription = "The constraint sorts returned entities by selected price for sale.", + userDocsLink = "/documentation/query/ordering/price#price-natural", supportedIn = { ConstraintDomain.ENTITY } ) public class PriceNatural extends AbstractOrderConstraintLeaf implements PriceConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/Random.java b/evita_query/src/main/java/io/evitadb/api/query/order/Random.java index ea745dadc..524760802 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/Random.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/Random.java @@ -49,6 +49,7 @@ @ConstraintDefinition( name = "random", shortDescription = "The constraint sorts returned entities randomly.", + userDocsLink = "/documentation/query/ordering/random#random", supportedIn = { ConstraintDomain.ENTITY, ConstraintDomain.REFERENCE } ) public class Random extends AbstractOrderConstraintLeaf implements GenericConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/ReferenceProperty.java b/evita_query/src/main/java/io/evitadb/api/query/order/ReferenceProperty.java index a5eb9bb79..acb618263 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/ReferenceProperty.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/ReferenceProperty.java @@ -116,6 +116,7 @@ @ConstraintDefinition( name = "property", shortDescription = "The constraint sorts returned entities or references by attribute specified on its reference in natural order.", + userDocsLink = "/documentation/query/ordering/reference#reference-property", supportedIn = { ConstraintDomain.ENTITY } ) public class ReferenceProperty extends AbstractOrderConstraintContainer implements ReferenceConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/AssociatedDataContent.java b/evita_query/src/main/java/io/evitadb/api/query/require/AssociatedDataContent.java index bdfb7ec17..5e10d6eab 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/AssociatedDataContent.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/AssociatedDataContent.java @@ -64,6 +64,7 @@ @ConstraintDefinition( name = "content", shortDescription = "The constraint triggers fetching the entity associated data of specified names into the returned entities.", + userDocsLink = "/documentation/query/requirements/fetching#associated-data-content", supportedIn = ConstraintDomain.ENTITY, supportedValues = @ConstraintSupportedValues(allTypesSupported = true, arraysSupported = true) ) diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/AttributeContent.java b/evita_query/src/main/java/io/evitadb/api/query/require/AttributeContent.java index fedb14cf0..0ebd9f8fb 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/AttributeContent.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/AttributeContent.java @@ -68,6 +68,7 @@ @ConstraintDefinition( name = "content", shortDescription = "The constraint triggers fetching the entity attributes into the returned entities.", + userDocsLink = "/documentation/query/requirements/fetching#attribute-content", supportedIn = ConstraintDomain.ENTITY, supportedValues = @ConstraintSupportedValues(allTypesSupported = true, arraysSupported = true) ) diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/AttributeHistogram.java b/evita_query/src/main/java/io/evitadb/api/query/require/AttributeHistogram.java index 28841cce6..a3e9c2337 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/AttributeHistogram.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/AttributeHistogram.java @@ -56,6 +56,7 @@ @ConstraintDefinition( name = "histogram", shortDescription = "The constraint triggers computation of the [histogram](https://en.wikipedia.org/wiki/Histogram) of specified attributes into response.", + userDocsLink = "/documentation/query/requirements/histogram#attribute-histogram", supportedValues = @ConstraintSupportedValues(supportedTypes = {Byte.class, Short.class, Integer.class, Long.class, BigDecimal.class}) ) public class AttributeHistogram extends AbstractRequireConstraintLeaf implements AttributeConstraint, ExtraResultRequireConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/DataInLocales.java b/evita_query/src/main/java/io/evitadb/api/query/require/DataInLocales.java index ba2ad3eed..2ca6551ad 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/DataInLocales.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/DataInLocales.java @@ -71,6 +71,7 @@ @ConstraintDefinition( name = "dataInLocales", shortDescription = "The constraint triggers fetching of the localized attributes or associated data in different/additional locales than the locale specified in filtering constraints (if any at all).", + userDocsLink = "/documentation/query/requirements/fetching#data-in-locales", supportedIn = ConstraintDomain.ENTITY ) public class DataInLocales extends AbstractRequireConstraintLeaf diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/EntityFetch.java b/evita_query/src/main/java/io/evitadb/api/query/require/EntityFetch.java index 4dafe5805..3d4fd4e18 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/EntityFetch.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/EntityFetch.java @@ -73,6 +73,7 @@ @ConstraintDefinition( name = "fetch", shortDescription = "Returns richer entities instead of just entity references (empty container returns only entity body).", + userDocsLink = "/documentation/query/requirements/fetching#entity-fetch", supportedIn = {ConstraintDomain.GENERIC, ConstraintDomain.REFERENCE, ConstraintDomain.HIERARCHY, ConstraintDomain.FACET} ) public class EntityFetch extends AbstractRequireConstraintContainer implements EntityFetchRequire { diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/EntityGroupFetch.java b/evita_query/src/main/java/io/evitadb/api/query/require/EntityGroupFetch.java index aabf29419..455779603 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/EntityGroupFetch.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/EntityGroupFetch.java @@ -77,6 +77,7 @@ @ConstraintDefinition( name = "groupFetch", shortDescription = "Returns richer group entities instead of just entity references (empty container returns only entity body).", + userDocsLink = "/documentation/query/requirements/fetching#entity-group-fetch", supportedIn = {ConstraintDomain.FACET} ) public class EntityGroupFetch extends AbstractRequireConstraintContainer implements EntityFetchRequire { diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsConjunction.java b/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsConjunction.java index f8fa834ae..ee8b244d6 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsConjunction.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsConjunction.java @@ -99,7 +99,8 @@ */ @ConstraintDefinition( name = "groupsConjunction", - shortDescription = "Sets inter-facets relation within the specified groups to [logical AND](https://en.wikipedia.org/wiki/Logical_conjunction)." + shortDescription = "Sets inter-facets relation within the specified groups to [logical AND](https://en.wikipedia.org/wiki/Logical_conjunction).", + userDocsLink = "/documentation/query/requirements/facet#facet-groups-conjunction" ) public class FacetGroupsConjunction extends AbstractRequireConstraintContainer implements FacetConstraint { @Serial private static final long serialVersionUID = -584073466325272463L; diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsDisjunction.java b/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsDisjunction.java index 9eb52e990..daf6cdff4 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsDisjunction.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsDisjunction.java @@ -99,7 +99,8 @@ */ @ConstraintDefinition( name = "groupsDisjunction", - shortDescription = "Sets relation of facets in the specified groups towards facets in different groups to [logical OR](https://en.wikipedia.org/wiki/Logical_disjunction) ." + shortDescription = "Sets relation of facets in the specified groups towards facets in different groups to [logical OR](https://en.wikipedia.org/wiki/Logical_disjunction) .", + userDocsLink = "/documentation/query/requirements/facet#facet-groups-disjunction" ) public class FacetGroupsDisjunction extends AbstractRequireConstraintContainer implements FacetConstraint { @Serial private static final long serialVersionUID = 1087282346634617160L; diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsNegation.java b/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsNegation.java index 6c7d8e920..c0b8d7cb4 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsNegation.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsNegation.java @@ -81,7 +81,8 @@ @ConstraintDefinition( name = "groupsNegation", shortDescription = "[Negates](https://en.wikipedia.org/wiki/Negation) the meaning of selected facets in specified " + - "facet groups in the sense that their selection would return entities that don't have any of those facets." + "facet groups in the sense that their selection would return entities that don't have any of those facets.", + userDocsLink = "/documentation/query/requirements/facet#facet-groups-negation" ) public class FacetGroupsNegation extends AbstractRequireConstraintContainer implements FacetConstraint { @Serial private static final long serialVersionUID = 3993873252481237893L; diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/FacetSummary.java b/evita_query/src/main/java/io/evitadb/api/query/require/FacetSummary.java index 6e3882517..29e79c31c 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/FacetSummary.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/FacetSummary.java @@ -127,7 +127,8 @@ */ @ConstraintDefinition( name = "summary", - shortDescription = "The constraint triggers computation of facet summary of all facet in searched scope into response with default \"fetching\" settings for all referenced entities." + shortDescription = "The constraint triggers computation of facet summary of all facet in searched scope into response with default \"fetching\" settings for all referenced entities.", + userDocsLink = "/documentation/query/requirements/facet#facet-summary" ) public class FacetSummary extends AbstractRequireConstraintContainer implements FacetConstraint, SeparateEntityContentRequireContainer, ExtraResultRequireConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/FacetSummaryOfReference.java b/evita_query/src/main/java/io/evitadb/api/query/require/FacetSummaryOfReference.java index 89b275a12..9e640d37e 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/FacetSummaryOfReference.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/FacetSummaryOfReference.java @@ -122,7 +122,8 @@ */ @ConstraintDefinition( name = "summary", - shortDescription = "The constraint triggers computation of facet summary of all facet in searched scope into response with custom \"fetching\" settings for specific reference." + shortDescription = "The constraint triggers computation of facet summary of all facet in searched scope into response with custom \"fetching\" settings for specific reference.", + userDocsLink = "/documentation/query/requirements/facet#facet-summary-of-reference" ) public class FacetSummaryOfReference extends AbstractRequireConstraintContainer implements FacetConstraint, SeparateEntityContentRequireContainer, ExtraResultRequireConstraint { @Serial private static final long serialVersionUID = 2377379601711709241L; diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyChildren.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyChildren.java index 1df1616f5..d3fd2fecd 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyChildren.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyChildren.java @@ -109,6 +109,7 @@ @ConstraintDefinition( name = "children", shortDescription = "The constraint triggers computing the hierarchy subtree starting at currently requested hierarchy node in filter by constraint.", + userDocsLink = "/documentation/query/requirements/hierarchy#children", supportedIn = ConstraintDomain.HIERARCHY ) public class HierarchyChildren extends AbstractRequireConstraintContainer implements HierarchyRequireConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyContent.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyContent.java index 655a0314b..bad3da43b 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyContent.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyContent.java @@ -71,6 +71,7 @@ @ConstraintDefinition( name = "content", shortDescription = "The constraint triggers fetching parent hierarchy entity parent chain and its bodies into returned main entities.", + userDocsLink = "/documentation/query/requirements/fetching#hierarchy-content", supportedIn = ConstraintDomain.ENTITY ) public class HierarchyContent extends AbstractRequireConstraintContainer implements HierarchyConstraint, SeparateEntityContentRequireContainer, EntityContentRequire { diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyDistance.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyDistance.java index f234167fe..fa223a046 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyDistance.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyDistance.java @@ -78,6 +78,7 @@ @ConstraintDefinition( name = "distance", shortDescription = "The constraint limits the traversing in stop at container at specified distance (number of nodes in path).", + userDocsLink = "/documentation/query/requirements/hierarchy#distance", supportedIn = ConstraintDomain.HIERARCHY ) public class HierarchyDistance extends AbstractRequireConstraintLeaf implements HierarchyStopAtRequireConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyFromNode.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyFromNode.java index f0301a113..02bae86aa 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyFromNode.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyFromNode.java @@ -130,6 +130,7 @@ @ConstraintDefinition( name = "fromNode", shortDescription = "The constraint triggers computing the hierarchy subtree starting at pivot node.", + userDocsLink = "/documentation/query/requirements/hierarchy#from-node", supportedIn = ConstraintDomain.HIERARCHY ) public class HierarchyFromNode extends AbstractRequireConstraintContainer implements HierarchyRequireConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyFromRoot.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyFromRoot.java index 3af30c1b6..19a2cfde6 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyFromRoot.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyFromRoot.java @@ -112,6 +112,7 @@ @ConstraintDefinition( name = "fromRoot", shortDescription = "The constraint triggers computing the hierarchy subtree starting at root level.", + userDocsLink = "/documentation/query/requirements/hierarchy#from-root", supportedIn = ConstraintDomain.HIERARCHY ) public class HierarchyFromRoot extends AbstractRequireConstraintContainer implements HierarchyRequireConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyLevel.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyLevel.java index adb49990c..aa2f611c3 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyLevel.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyLevel.java @@ -71,6 +71,7 @@ @ConstraintDefinition( name = "level", shortDescription = "The constraint limits the traversing in stop at container at specified level from root.", + userDocsLink = "/documentation/query/requirements/hierarchy#level", supportedIn = ConstraintDomain.HIERARCHY ) public class HierarchyLevel extends AbstractRequireConstraintLeaf implements HierarchyStopAtRequireConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyNode.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyNode.java index 574373045..0de8b2bb5 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyNode.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyNode.java @@ -83,6 +83,7 @@ @ConstraintDefinition( name = "node", shortDescription = "The constraint allows to locate the pivot hierarchy node.", + userDocsLink = "/documentation/query/requirements/hierarchy#node", supportedIn = ConstraintDomain.HIERARCHY ) public class HierarchyNode extends AbstractRequireConstraintContainer implements HierarchyStopAtRequireConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyOfReference.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyOfReference.java index fc566200a..55d5e4eb2 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyOfReference.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyOfReference.java @@ -84,7 +84,8 @@ */ @ConstraintDefinition( name = "ofReference", - shortDescription = "The constraint triggers computation of hierarchy statistics (how many matching children the hierarchy nodes have) of referenced hierarchical entities into response." + shortDescription = "The constraint triggers computation of hierarchy statistics (how many matching children the hierarchy nodes have) of referenced hierarchical entities into response.", + userDocsLink = "/documentation/query/requirements/hierarchy#hierarchy-of-reference" ) public class HierarchyOfReference extends AbstractRequireConstraintContainer implements RootHierarchyConstraint, SeparateEntityContentRequireContainer, ExtraResultRequireConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyOfSelf.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyOfSelf.java index 3ee3ffc2f..0a3729c48 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyOfSelf.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyOfSelf.java @@ -75,7 +75,8 @@ */ @ConstraintDefinition( name = "ofSelf", - shortDescription = "The constraint triggers computation of hierarchy statistics (how many matching children the hierarchy nodes have) of same hierarchical collection into response." + shortDescription = "The constraint triggers computation of hierarchy statistics (how many matching children the hierarchy nodes have) of same hierarchical collection into response.", + userDocsLink = "/documentation/query/requirements/hierarchy#hierarchy-of-self" ) public class HierarchyOfSelf extends AbstractRequireConstraintContainer implements RootHierarchyConstraint, SeparateEntityContentRequireContainer, ExtraResultRequireConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyParents.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyParents.java index 0ea83fa2b..4b7ea73f3 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyParents.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyParents.java @@ -106,6 +106,7 @@ @ConstraintDefinition( name = "parents", shortDescription = "The constraint triggers computing the hierarchy parent axis starting at currently requested hierarchy node in filter by constraint.", + userDocsLink = "/documentation/query/requirements/hierarchy#parents", supportedIn = ConstraintDomain.HIERARCHY ) public class HierarchyParents extends AbstractRequireConstraintContainer implements HierarchyRequireConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchySiblings.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchySiblings.java index 1bd94ce2e..4bdfcb144 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchySiblings.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchySiblings.java @@ -116,6 +116,7 @@ @ConstraintDefinition( name = "siblings", shortDescription = "The constraint triggers computing the sibling axis for currently requested hierarchy node in filter by constraint or processed node by hierarchy parents axis.", + userDocsLink = "/documentation/query/requirements/hierarchy#siblings", supportedIn = ConstraintDomain.HIERARCHY ) public class HierarchySiblings extends AbstractRequireConstraintContainer implements HierarchyRequireConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyStatistics.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyStatistics.java index 1e0ef9588..160726022 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyStatistics.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyStatistics.java @@ -84,6 +84,7 @@ @ConstraintDefinition( name = "statistics", shortDescription = "The constraint triggers computing the count of children for each returned hierarchy node.", + userDocsLink = "/documentation/query/requirements/hierarchy#statistics", supportedIn = ConstraintDomain.HIERARCHY ) public class HierarchyStatistics extends AbstractRequireConstraintLeaf implements HierarchyOutputRequireConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyStopAt.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyStopAt.java index 2c655f108..3e548d070 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyStopAt.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyStopAt.java @@ -55,6 +55,7 @@ @ConstraintDefinition( name = "stopAt", shortDescription = "The constraint defines the traversal stop condition that limits the scope of the returned hierarchy tree.", + userDocsLink = "/documentation/query/requirements/hierarchy#stop-at", supportedIn = ConstraintDomain.HIERARCHY ) public class HierarchyStopAt extends AbstractRequireConstraintContainer implements HierarchyOutputRequireConstraint { diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/Page.java b/evita_query/src/main/java/io/evitadb/api/query/require/Page.java index bad68d0d4..d4c5db199 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/Page.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/Page.java @@ -58,7 +58,8 @@ */ @ConstraintDefinition( name = "page", - shortDescription = "The constraint specifies which page of found entities will be returned." + shortDescription = "The constraint specifies which page of found entities will be returned.", + userDocsLink = "/documentation/query/requirements/paging#page" ) public class Page extends AbstractRequireConstraintLeaf implements GenericConstraint, ChunkingRequireConstraint { @Serial private static final long serialVersionUID = 1300354074537839696L; diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/PriceContent.java b/evita_query/src/main/java/io/evitadb/api/query/require/PriceContent.java index a5d85c165..f93fe7d39 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/PriceContent.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/PriceContent.java @@ -70,6 +70,7 @@ @ConstraintDefinition( name = "content", shortDescription = "The constraint triggers fetching the entity prices into the returned entities.", + userDocsLink = "/documentation/query/requirements/fetching#price-content", supportedIn = ConstraintDomain.ENTITY ) public class PriceContent extends AbstractRequireConstraintLeaf diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/PriceHistogram.java b/evita_query/src/main/java/io/evitadb/api/query/require/PriceHistogram.java index 1fd28de26..bd287353d 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/PriceHistogram.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/PriceHistogram.java @@ -53,7 +53,8 @@ */ @ConstraintDefinition( name = "histogram", - shortDescription = "The constraint triggers computation of the [histogram](https://en.wikipedia.org/wiki/Histogram) of price for sale into response." + shortDescription = "The constraint triggers computation of the [histogram](https://en.wikipedia.org/wiki/Histogram) of price for sale into response.", + userDocsLink = "/documentation/query/requirements/histogram#price-histogram" ) public class PriceHistogram extends AbstractRequireConstraintLeaf implements PriceConstraint, ExtraResultRequireConstraint { @Serial private static final long serialVersionUID = 7734875430759525982L; diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/PriceType.java b/evita_query/src/main/java/io/evitadb/api/query/require/PriceType.java index 5376f5edf..ca7f08a2b 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/PriceType.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/PriceType.java @@ -54,7 +54,8 @@ */ @ConstraintDefinition( name = "type", - shortDescription = "The constraint specifies which price type (with/without tax) will be used for handling filtering and sorting constraints." + shortDescription = "The constraint specifies which price type (with/without tax) will be used for handling filtering and sorting constraints.", + userDocsLink = "/documentation/query/requirements/price#price-type" ) public class PriceType extends AbstractRequireConstraintLeaf implements PriceConstraint { @Serial private static final long serialVersionUID = -7156758352138266166L; diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/QueryTelemetry.java b/evita_query/src/main/java/io/evitadb/api/query/require/QueryTelemetry.java index 004b91028..8d6534ab3 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/QueryTelemetry.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/QueryTelemetry.java @@ -46,7 +46,8 @@ */ @ConstraintDefinition( name = "queryTelemetry", - shortDescription = "The constraint triggers computation of query telemetry (explaining what operations were performed and how long they took) in extra results of the response." + shortDescription = "The constraint triggers computation of query telemetry (explaining what operations were performed and how long they took) in extra results of the response.", + userDocsLink = "/documentation/query/requirements/debug#query-telemetry" ) public class QueryTelemetry extends AbstractRequireConstraintLeaf implements GenericConstraint { @Serial private static final long serialVersionUID = -5121347556508500340L; diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/ReferenceContent.java b/evita_query/src/main/java/io/evitadb/api/query/require/ReferenceContent.java index dc28bf699..65fb75e14 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/ReferenceContent.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/ReferenceContent.java @@ -154,6 +154,7 @@ @ConstraintDefinition( name = "content", shortDescription = "The constraint triggers fetching referenced entity bodies into returned main entities.", + userDocsLink = "/documentation/query/requirements/fetching#reference-content", supportedIn = ConstraintDomain.ENTITY ) public class ReferenceContent extends AbstractRequireConstraintContainer diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/Require.java b/evita_query/src/main/java/io/evitadb/api/query/require/Require.java index 9b6172050..ae518fdea 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/Require.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/Require.java @@ -54,7 +54,8 @@ */ @ConstraintDefinition( name = "require", - shortDescription = "The container encapsulates inner require constraints into one main constraint that is required by the query" + shortDescription = "The container encapsulates inner require constraints into one main constraint that is required by the query", + userDocsLink = "/documentation/query/basics#require" ) public class Require extends AbstractRequireConstraintContainer implements GenericConstraint { @Serial private static final long serialVersionUID = 6115101893250263038L; diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/Strip.java b/evita_query/src/main/java/io/evitadb/api/query/require/Strip.java index 71d4962fe..ba5d532f4 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/Strip.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/Strip.java @@ -56,7 +56,8 @@ */ @ConstraintDefinition( name = "strip", - shortDescription = "The constraint specifies which strip (subset) of found entities will be returned." + shortDescription = "The constraint specifies which strip (subset) of found entities will be returned.", + userDocsLink = "/documentation/query/requirements/paging#strip" ) public class Strip extends AbstractRequireConstraintLeaf implements GenericConstraint, ChunkingRequireConstraint { @Serial private static final long serialVersionUID = 1300354074537839696L; From efd1c36e385a6a83fa32640d014013268b65f4ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Novotn=C3=BD?= Date: Fri, 8 Dec 2023 10:08:00 +0100 Subject: [PATCH 02/52] fix: fixed logic for setting reference with cardinality one When the reference is replaced the old reference was not cleared. --- .../proxy/impl/AbstractEntityProxyState.java | 18 ++++++ .../SetReferenceMethodClassifier.java | 57 +++++++++++++++++-- 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/evita_api/src/main/java/io/evitadb/api/proxy/impl/AbstractEntityProxyState.java b/evita_api/src/main/java/io/evitadb/api/proxy/impl/AbstractEntityProxyState.java index d75cbe4c3..1fbcb52db 100644 --- a/evita_api/src/main/java/io/evitadb/api/proxy/impl/AbstractEntityProxyState.java +++ b/evita_api/src/main/java/io/evitadb/api/proxy/impl/AbstractEntityProxyState.java @@ -475,6 +475,24 @@ public void registerReferencedEntityObject( ); } + /** + * Method unregisters created proxy object that was created by this proxy instance and relates to referenced objects + * accessed via it. + * + * @param referencedEntityType the {@link EntitySchemaContract#getName()} of the referenced entity type + * @param referencedPrimaryKey the {@link EntityContract#getPrimaryKey()} of the referenced entity + * @param logicalType logical type of the proxy object + */ + public void unregisterReferencedEntityObject( + @Nonnull String referencedEntityType, + int referencedPrimaryKey, + @Nonnull ProxyType logicalType + ) { + generatedProxyObjects.remove( + new ProxyInstanceCacheKey(referencedEntityType, referencedPrimaryKey, logicalType) + ); + } + @Override @Nonnull public Stream getReferencedEntityBuildersWithCallback() { diff --git a/evita_api/src/main/java/io/evitadb/api/proxy/impl/entityBuilder/SetReferenceMethodClassifier.java b/evita_api/src/main/java/io/evitadb/api/proxy/impl/entityBuilder/SetReferenceMethodClassifier.java index 429ce279b..1a3470e41 100644 --- a/evita_api/src/main/java/io/evitadb/api/proxy/impl/entityBuilder/SetReferenceMethodClassifier.java +++ b/evita_api/src/main/java/io/evitadb/api/proxy/impl/entityBuilder/SetReferenceMethodClassifier.java @@ -940,7 +940,7 @@ private static void setReferencedEntityClassifier( ) { final EntityBuilder entityBuilder = theState.entityBuilder(); final EntityClassifier referencedClassifier = (EntityClassifier) args[0]; - if (referencedClassifier == null && cardinality == Cardinality.ZERO_OR_ONE) { + if (referencedClassifier == null && (cardinality == Cardinality.ZERO_OR_ONE || cardinality == Cardinality.EXACTLY_ONE)) { final Collection references = entityBuilder.getReferences(referenceName); Assert.isPremiseValid(references.size() < 2, "Cardinality is `" + cardinality + "` but there are more than one reference!"); references.forEach(it -> entityBuilder.removeReference(referenceName, it.getReferencedPrimaryKey())); @@ -950,6 +950,22 @@ private static void setReferencedEntityClassifier( "Entity type `" + referencedClassifier.getType() + "` in passed argument " + "doesn't match the referenced entity type: `" + expectedEntityType + "`!" ); + + if (cardinality == Cardinality.ZERO_OR_ONE || cardinality == Cardinality.EXACTLY_ONE) { + final Collection references = entityBuilder.getReferences(referenceName); + Assert.isPremiseValid(references.size() < 2, "Cardinality is `" + cardinality + "` but there are more than one reference!"); + if (!references.isEmpty()) { + final ReferenceContract singleReference = references.iterator().next(); + if (singleReference.getReferencedPrimaryKey() == referencedClassifier.getPrimaryKey()) { + // do nothing the reference is already set + return; + } else { + // remove existing reference and registered object + entityBuilder.removeReference(referenceName, singleReference.getReferencedPrimaryKey()); + } + } + } + entityBuilder.setReference(referenceName, referencedClassifier.getPrimaryKey()); } } @@ -1137,14 +1153,30 @@ private static void setReferencedEntityPrimaryKey( ) { final EntityBuilder entityBuilder = theState.entityBuilder(); final Serializable referencedClassifier = (Serializable) args[0]; - if (referencedClassifier == null && cardinality == Cardinality.ZERO_OR_ONE) { + if (referencedClassifier == null && (cardinality == Cardinality.ZERO_OR_ONE || cardinality == Cardinality.EXACTLY_ONE)) { final Collection references = entityBuilder.getReferences(referenceName); Assert.isPremiseValid(references.size() < 2, "Cardinality is `" + cardinality + "` but there are more than one reference!"); references.forEach(it -> entityBuilder.removeReference(referenceName, it.getReferencedPrimaryKey())); } else { + final Integer newReferencedPrimaryKey = EvitaDataTypes.toTargetType(referencedClassifier, int.class); + if (cardinality == Cardinality.ZERO_OR_ONE || cardinality == Cardinality.EXACTLY_ONE) { + final Collection references = entityBuilder.getReferences(referenceName); + Assert.isPremiseValid(references.size() < 2, "Cardinality is `" + cardinality + "` but there are more than one reference!"); + if (!references.isEmpty()) { + final ReferenceContract singleReference = references.iterator().next(); + if (singleReference.getReferencedPrimaryKey() == newReferencedPrimaryKey) { + // do nothing the reference is already set + return; + } else { + // remove existing reference and registered object + entityBuilder.removeReference(referenceName, singleReference.getReferencedPrimaryKey()); + } + } + } + entityBuilder.setReference( referenceName, - EvitaDataTypes.toTargetType(referencedClassifier, int.class) + newReferencedPrimaryKey ); } } @@ -1321,11 +1353,28 @@ private static void setReferencedEntity( ) { final EntityBuilder entityBuilder = theState.entityBuilder(); final SealedEntityProxy referencedEntity = (SealedEntityProxy) args[0]; - if (referencedEntity == null && cardinality == Cardinality.ZERO_OR_ONE) { + if (referencedEntity == null && (cardinality == Cardinality.ZERO_OR_ONE || cardinality == Cardinality.EXACTLY_ONE)) { final Collection references = entityBuilder.getReferences(referenceName); Assert.isPremiseValid(references.size() < 2, "Cardinality is `" + cardinality + "` but there are more than one reference!"); references.forEach(it -> entityBuilder.removeReference(referenceName, it.getReferencedPrimaryKey())); } else { + final Collection references = entityBuilder.getReferences(referenceName); + if (cardinality == Cardinality.ZERO_OR_ONE || cardinality == Cardinality.EXACTLY_ONE) { + Assert.isPremiseValid(references.size() < 2, "Cardinality is `" + cardinality + "` but there are more than one reference!"); + if (!references.isEmpty()) { + final ReferenceContract singleReference = references.iterator().next(); + if (singleReference.getReferencedPrimaryKey() == referencedEntity.getPrimaryKey()) { + // just exchange registered object - the set entity and existing reference share same primary key + theState.registerReferencedEntityObject(expectedEntityType, singleReference.getReferencedPrimaryKey(), referencedEntity, ProxyType.REFERENCED_ENTITY); + return; + } else { + // remove existing reference and registered object + entityBuilder.removeReference(referenceName, singleReference.getReferencedPrimaryKey()); + theState.unregisterReferencedEntityObject(expectedEntityType, singleReference.getReferencedPrimaryKey(), ProxyType.REFERENCED_ENTITY); + } + } + } + final EntityContract sealedEntity = referencedEntity.entity(); Assert.isTrue( expectedEntityType.equals(sealedEntity.getType()), From 56eb126aa93ca3934e4f4ec95bd2c61fed9ab760 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Novotn=C3=BD?= Date: Fri, 8 Dec 2023 10:32:44 +0100 Subject: [PATCH 03/52] docs: updated documentation --- .../example/imperative-schema-definition.java | 5 +++ .../user/en/use/api/example/parent-class.java | 20 +++++++++++ .../en/use/api/example/parent-interface.java | 35 +++++++++++++++++++ .../en/use/api/example/parent-record.java | 18 ++++++++++ documentation/user/en/use/api/query-data.md | 17 ++++++++- .../documentation/UserDocumentationTest.java | 2 +- 6 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 documentation/user/en/use/api/example/parent-class.java create mode 100644 documentation/user/en/use/api/example/parent-interface.java create mode 100644 documentation/user/en/use/api/example/parent-record.java diff --git a/documentation/user/en/use/api/example/imperative-schema-definition.java b/documentation/user/en/use/api/example/imperative-schema-definition.java index d8204c355..3b42430bb 100644 --- a/documentation/user/en/use/api/example/imperative-schema-definition.java +++ b/documentation/user/en/use/api/example/imperative-schema-definition.java @@ -1,6 +1,11 @@ evita.updateCatalog( "testCatalog", session -> { + + /* first create stubs of the entity schemas that the product will reference */ + session.defineEntitySchema("Brand"); + session.defineEntitySchema("Category"); + session.defineEntitySchema("Product") /* all is strictly verified but associated data and references can be added on the fly */ diff --git a/documentation/user/en/use/api/example/parent-class.java b/documentation/user/en/use/api/example/parent-class.java new file mode 100644 index 000000000..81e3d6a9d --- /dev/null +++ b/documentation/user/en/use/api/example/parent-class.java @@ -0,0 +1,20 @@ +@Data +public class MyEntity { + + // contains id of parent entity, or null if this entity is a root entity + @ParentEntity + @Nullable private final Integer parentId(), + + // contains parent entity, or null if this entity is a root entity + @ParentEntity + @Nullable private final SealedEntity parentEntity(), + + // contains reference to a parent entity, or null if this entity is a root entity + @ParentEntity + @Nullable private final EntityReference parentEntityReference(), + + // contains reference to a parent wrapped in this interface, or null if this entity is a root entity + @ParentEntity + @Nullable private final MyEntity parentMyEntity() + +} \ No newline at end of file diff --git a/documentation/user/en/use/api/example/parent-interface.java b/documentation/user/en/use/api/example/parent-interface.java new file mode 100644 index 000000000..9a58d281d --- /dev/null +++ b/documentation/user/en/use/api/example/parent-interface.java @@ -0,0 +1,35 @@ +public interface MyEntity { + + // return id of parent entity, or null if this entity is a root entity + @ParentEntity + @Nullable Integer getParentId(); + + // return optional id of parent entity, or empty if this entity is a root entity + @ParentEntity + @Nonnull OptionalInt getParentIdIfNotRoot(); + + // return parent entity, or null if this entity is a root entity + @ParentEntity + @Nullable SealedEntity getParentEntity(); + + // return optional parent entity, or empty if this entity is a root entity + @ParentEntity + @Nonnull Optional getParentEntityIfNotRoot(); + + // return reference to a parent entity, or null if this entity is a root entity + @ParentEntity + @Nullable EntityReference getParentEntityReference(); + + // return optional reference to a parent entity, or empty if this entity is a root entity + @ParentEntity + @Nonnull Optional getParentEntityReferenceIfPresent(); + + // return reference to a parent wrapped in this interface, or null if this entity is a root entity + @ParentEntity + @Nullable MyEntity getParentMyEntity(); + + // return optional reference to a parent wrapped in this interface, or empty if this entity is a root entity + @ParentEntity + @Nonnull Optional getParentMyEntityIfPresent(); + +} \ No newline at end of file diff --git a/documentation/user/en/use/api/example/parent-record.java b/documentation/user/en/use/api/example/parent-record.java new file mode 100644 index 000000000..1b5f79d54 --- /dev/null +++ b/documentation/user/en/use/api/example/parent-record.java @@ -0,0 +1,18 @@ +public record MyEntity( + // contains id of parent entity, or null if this entity is a root entity + @ParentEntity + @Nullable Integer parentId(), + + // contains parent entity, or null if this entity is a root entity + @ParentEntity + @Nullable SealedEntity parentEntity(), + + // contains reference to a parent entity, or null if this entity is a root entity + @ParentEntity + @Nullable EntityReference parentEntityReference(), + + // contains reference to a parent wrapped in this interface, or null if this entity is a root entity + @ParentEntity + @Nullable MyEntity parentMyEntity() +) { +} \ No newline at end of file diff --git a/documentation/user/en/use/api/query-data.md b/documentation/user/en/use/api/query-data.md index 901463178..0ceb59638 100644 --- a/documentation/user/en/use/api/query-data.md +++ b/documentation/user/en/use/api/query-data.md @@ -366,7 +366,22 @@ TODO JNO - Work in progress #### Hierarchy -TODO JNO - Work in progress +To access the placement information of the entity or hierarchy (i.e., its parent), you must use either the numeric data +type, your own custom interface type, evita_api/src/main/java/io/evitadb/api/requestResponse/data/SealedEntity.java +or evita_api/src/main/java/io/evitadb/api/requestResponse/data/structure/EntityReference.java +data type and annotate it with the `@ParentEntity` annotation. The datatype can be wrapped in +[Optional](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Optional.html) +(or its counterparts [OptionalInt](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/OptionalInt.html) +or [OptionalLong](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/OptionalLong.html)). + + + +[Example interface with parent access](/documentation/user/en/use/api/example/parent-interface.java) + + + +The method may return null if the entity is a root entity. Therefore, it's not recommended to use primitive data types, +because the method call may fail with a `NullPointerException` in such a case. #### References 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 53c404f96..afd3e8929 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 @@ -359,7 +359,7 @@ Stream testDocumentation() throws IOException { Stream testSingleFileDocumentation() { return this.createTests( DocumentationProfile.DEFAULT, - getRootDirectory().resolve("documentation/user/en/operate/monitor.md"), + getRootDirectory().resolve("documentation/user/en/use/api/schema-api.md"), ExampleFilter.values() ).stream(); } From 9c75bb09781bf1e88a965bdb8dd9e1d852ee538f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Fri, 8 Dec 2023 11:13:00 +0100 Subject: [PATCH 04/52] docs: query telemetry docs --- documentation/user/en/menu.json | 4 + documentation/user/en/query/basics.md | 9 +- .../examples/telemetry/queryTelemetry.cs | 17 ++++ .../examples/telemetry/queryTelemetry.evitaql | 12 +++ .../examples/telemetry/queryTelemetry.graphql | 16 ++++ .../telemetry/queryTelemetry.graphql.json.md | 80 ++++++++++++++++ .../examples/telemetry/queryTelemetry.java | 19 ++++ .../examples/telemetry/queryTelemetry.rest | 15 +++ .../telemetry/queryTelemetry.rest.json.md | 80 ++++++++++++++++ .../user/en/query/requirements/price.md | 16 ++-- .../user/en/query/requirements/telemetry.md | 92 +++++++++++++++++++ .../ConstraintDescriptorProviderTest.java | 3 +- .../descriptor/ConstraintDescriptorTest.java | 2 + .../descriptor/ConstraintProcessorTest.java | 63 +++++++++---- .../evitadb/documentation/JsonExecutable.java | 4 +- .../api/query/require/QueryTelemetry.java | 2 +- 16 files changed, 403 insertions(+), 31 deletions(-) create mode 100644 documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.cs create mode 100644 documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.evitaql create mode 100644 documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.graphql create mode 100644 documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.graphql.json.md create mode 100644 documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.java create mode 100644 documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.rest create mode 100644 documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.rest.json.md create mode 100644 documentation/user/en/query/requirements/telemetry.md diff --git a/documentation/user/en/menu.json b/documentation/user/en/menu.json index 2bc7b12b4..03fe2933f 100644 --- a/documentation/user/en/menu.json +++ b/documentation/user/en/menu.json @@ -190,6 +190,10 @@ { "title": "Histogram", "path": "/query/requirements/histogram.md" + }, + { + "title": "Telemetry", + "path": "/query/requirements/telemetry.md" } ] } diff --git a/documentation/user/en/query/basics.md b/documentation/user/en/query/basics.md index 672feea90..45fc285c6 100644 --- a/documentation/user/en/query/basics.md +++ b/documentation/user/en/query/basics.md @@ -560,4 +560,11 @@ aggregated by their numeric value in a particular attribute or by their sales pr The price requirement controls which form of price for sale is taken into account when entities are filtered, ordered, or their histograms are calculated: -- [price type](requirements/price.md#price-type) \ No newline at end of file +- [price type](requirements/price.md#price-type) + +### Telemetry + +The telemetry requirements trigger the calculation of additional telemetry data for looking under the hood of the +database engine: + +- [query telemetry](requirements/telemetry.md#query-telemetry) \ No newline at end of file diff --git a/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.cs b/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.cs new file mode 100644 index 000000000..dc152f893 --- /dev/null +++ b/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.cs @@ -0,0 +1,17 @@ +EvitaResponse entities = evita.QueryCatalog( + "evita", + session => session.QuerySealedEntity( + Query( + Collection("Product"), + FilterBy( + AttributeStartsWith("code", "garmin") + ), + OrderBy( + AttributeNatural("code", Asc) + ), + Require( + QueryTelemetry() + ) + ) + ) +); \ No newline at end of file diff --git a/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.evitaql b/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.evitaql new file mode 100644 index 000000000..b23305c06 --- /dev/null +++ b/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.evitaql @@ -0,0 +1,12 @@ +query( + collection("Product"), + filterBy( + attributeStartsWith("code", "garmin") + ), + orderBy( + attributeNatural("code") + ), + require( + queryTelemetry() + ) +) \ No newline at end of file diff --git a/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.graphql b/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.graphql new file mode 100644 index 000000000..ff2f22de7 --- /dev/null +++ b/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.graphql @@ -0,0 +1,16 @@ +{ + queryProduct( + filterBy: { + attributeCodeStartsWith: "garmin" + }, + orderBy: [ + { + attributeCodeNatural: ASC + } + ] + ) { + extraResults { + queryTelemetry + } + } +} \ No newline at end of file diff --git a/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.graphql.json.md b/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.graphql.json.md new file mode 100644 index 000000000..d023e09b7 --- /dev/null +++ b/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.graphql.json.md @@ -0,0 +1,80 @@ +```json +{ + "operation": "OVERALL", + "start": 177425818030395, + "steps": [ + { + "operation": "PLANNING", + "start": 177425818039060, + "steps": [ + { + "operation": "PLANNING_INDEX_USAGE", + "start": 177425818040553, + "steps": [ ], + "arguments": [ ], + "spentTime": "10129" + }, + { + "operation": "PLANNING_FILTER", + "start": 177425818096047, + "steps": [ + { + "operation": "PLANNING_FILTER_ALTERNATIVE", + "start": 177425818101938, + "steps": [ ], + "arguments": [ + "Index type: GLOBAL, estimated costs 585" + ], + "spentTime": "953217" + } + ], + "arguments": [ + "Selected index: Index type: GLOBAL, estimated costs 585" + ], + "spentTime": "969337" + }, + { + "operation": "PLANNING_SORT", + "start": 177425819073169, + "steps": [ ], + "arguments": [ ], + "spentTime": "184534" + }, + { + "operation": "PLANNING_EXTRA_RESULT_FABRICATION", + "start": 177425819259727, + "steps": [ ], + "arguments": [ ], + "spentTime": "31859" + } + ], + "arguments": [ ], + "spentTime": "1256193" + }, + { + "operation": "EXECUTION", + "start": 177425819296625, + "steps": [ + { + "operation": "EXECUTION_FILTER", + "start": 177425819301374, + "steps": [ ], + "arguments": [ ], + "spentTime": "45776" + }, + { + "operation": "EXECUTION_SORT_AND_SLICE", + "start": 177425819347701, + "steps": [ ], + "arguments": [ ], + "spentTime": "500843" + } + ], + "arguments": [ ], + "spentTime": "616930" + } + ], + "arguments": [ ], + "spentTime": "1883812" +} +``` \ No newline at end of file diff --git a/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.java b/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.java new file mode 100644 index 000000000..28923d7be --- /dev/null +++ b/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.java @@ -0,0 +1,19 @@ +final EvitaResponse entities = evita.queryCatalog( + "evita", + session -> { + return session.querySealedEntity( + query( + collection("Product"), + filterBy( + attributeStartsWith("code", "garmin") + ), + orderBy( + attributeNatural("code", ASC) + ), + require( + queryTelemetry() + ) + ) + ); + } +); \ No newline at end of file diff --git a/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.rest b/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.rest new file mode 100644 index 000000000..4ac463b6b --- /dev/null +++ b/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.rest @@ -0,0 +1,15 @@ +POST /rest/evita/Product/query + +{ + "filterBy" : { + "attributeCodeStartsWith" : "garmin" + }, + "orderBy" : [ + { + "attributeCodeNatural" : "ASC" + } + ], + "require" : { + "queryTelemetry" : true + } +} \ No newline at end of file diff --git a/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.rest.json.md b/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.rest.json.md new file mode 100644 index 000000000..f3c2f1204 --- /dev/null +++ b/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.rest.json.md @@ -0,0 +1,80 @@ +```json +{ + "operation": "OVERALL", + "start": 177425764174411, + "steps": [ + { + "operation": "PLANNING", + "start": 177425764186033, + "steps": [ + { + "operation": "PLANNING_INDEX_USAGE", + "start": 177425764188768, + "steps": [ ], + "arguments": [ ], + "spentTime": "0.000012364s" + }, + { + "operation": "PLANNING_FILTER", + "start": 177425764211030, + "steps": [ + { + "operation": "PLANNING_FILTER_ALTERNATIVE", + "start": 177425764217070, + "steps": [ ], + "arguments": [ + "Index type: GLOBAL, estimated costs 585" + ], + "spentTime": "0.000986590s" + } + ], + "arguments": [ + "Selected index: Index type: GLOBAL, estimated costs 585" + ], + "spentTime": "0.001002448s" + }, + { + "operation": "PLANNING_SORT", + "start": 177425765215422, + "steps": [ ], + "arguments": [ ], + "spentTime": "0.000113883s" + }, + { + "operation": "PLANNING_EXTRA_RESULT_FABRICATION", + "start": 177425765330918, + "steps": [ ], + "arguments": [ ], + "spentTime": "0.000009688s" + } + ], + "arguments": [ ], + "spentTime": "0.001157799s" + }, + { + "operation": "EXECUTION", + "start": 177425765381451, + "steps": [ + { + "operation": "EXECUTION_FILTER", + "start": 177425765386741, + "steps": [ ], + "arguments": [ ], + "spentTime": "0.000043041s" + }, + { + "operation": "EXECUTION_SORT_AND_SLICE", + "start": 177425765430594, + "steps": [ ], + "arguments": [ ], + "spentTime": "0.000157423s" + } + ], + "arguments": [ ], + "spentTime": "0.000262340s" + } + ], + "arguments": [ ], + "spentTime": "0.001470131s" +} +``` \ No newline at end of file diff --git a/documentation/user/en/query/requirements/price.md b/documentation/user/en/query/requirements/price.md index c978e06c1..c22f94fef 100644 --- a/documentation/user/en/query/requirements/price.md +++ b/documentation/user/en/query/requirements/price.md @@ -2,15 +2,15 @@ title: Price date: '7.11.2023' perex: | - In B2C scenarios, prices are generally displayed with tax included to give consumers the total purchase cost upfront, - adhering to retail standards and regulations. For the B2B subset, displaying prices without tax is critical as it + In B2C scenarios, prices are generally displayed with tax included to give consumers the total purchase cost upfront, + adhering to retail standards and regulations. For the B2B subset, displaying prices without tax is critical as it aligns with their financial processes and allows them to manage tax reclaim separately. author: 'Ing. Jan Novotný' proofreading: 'done' preferredLang: 'evitaql' --- -That's why we need to control what type of price we're working with in our queries, since it would produce different +That's why we need to control what type of price we're working with in our queries, since it would produce different results for different settings. The [`priceType`](../requirements/price.md#price-type) requirement allows us to do this. ## Price type @@ -24,17 +24,17 @@ priceType(

argument:enum(WITH_TAX|WITHOUT_TAX)!
- selection of the type of price that should be taken into account when calculating the selling price and + selection of the type of price that should be taken into account when calculating the selling price and filtering or sorting by it
-The evita_query/src/main/java/io/evitadb/api/query/require/PriceType.javaEvitaDB.Client/Queries/Requires/PriceType.cs requirement -controls which price type is used when calculating the sales price and filtering or sorting by it. If no such +The evita_query/src/main/java/io/evitadb/api/query/require/PriceType.javaEvitaDB.Client/Queries/Requires/PriceType.cs requirement +controls which price type is used when calculating the sales price and filtering or sorting by it. If no such requirement is specified, **the price with tax is used by default**. -heloTo demonstrate the effect of this requirement, let's say the user wants to find all products with a selling price +To demonstrate the effect of this requirement, let's say the user wants to find all products with a selling price between `€100` and `€105`. The following query will do that: @@ -88,7 +88,7 @@ in this range with the price without tax. To do this, we need to modify the quer -And now the result contains completely different products (in the output we show the price with tax to demonstrate the +And now the result contains completely different products (in the output we show the price with tax to demonstrate the difference - in regular UI you'd choose to show the price without tax) with more appropriate price for particular user: diff --git a/documentation/user/en/query/requirements/telemetry.md b/documentation/user/en/query/requirements/telemetry.md new file mode 100644 index 000000000..96c614ed5 --- /dev/null +++ b/documentation/user/en/query/requirements/telemetry.md @@ -0,0 +1,92 @@ +--- +title: Telemetry +date: '7.12.2023' +perex: | + When you operate a complex database system, you often need to know what is happening under the hood of the database engine, + so you can optimize your queries and so on. Telemetry is a toolset that helps you to understand how your actions + are planned and executed. +author: 'Bc. Lukáš Hornych' +proofreading: 'done' +preferredLang: 'evitaql' +--- + +## Query telemetry + +```evitaql-syntax +queryTelemetry() +``` + +The evita_query/src/main/java/io/evitadb/api/query/require/QueryTelemetry.java requirement +EvitaDB.Client/Queries/Requires/QueryTelemetry.cs requirement +`queryTelemetry` extra result field +requests the computed query telemetry for the current query. The telemetry contains detailed information about the query +processing time and its decomposition to single operations. + +The query telemetry object represents a single executed operation with possibly nested other operations and consists of +the following data: + +
+
operation
+
+ Phase of the query execution. + Possible values can be found in the evita_api/src/main/java/io/evitadb/api/requestResponse/extraResult/QueryTelemetry.java class + EvitaDB.Client/Models/ExtraResults/QueryTelemetry.cs class. +
+
start
+
+ Date and time of the start of this step in nanoseconds. +
+
steps
+
+ Internal steps of this telemetry step (operation decomposition). Same structure as the parent telemetry object. +
+
arguments
+
+ Arguments of the processing phase. +
+
spentTime
+
+ Duration in nanoseconds. +
+
+ + +By default the number values of the telemetry object are returned in raw form. You can change that in the GraphQL by +using argument `format` on the `queryTelemetry` field. This way human-readable values are returned. + + +To demonstrate the information the query telemetry is providing, we will use the following query that filters and sorts +entities: + + + +[Example query to compute query telemetry for complex filtering and ordering](/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.evitaql) + + + + + + +##### Result query telemetry for filtered and ordered entities + + + +The result contains query telemetry and some products (which we omitted here for brevity): + + + +[Result query telemetry for filtered and ordered entities](/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.evitaql.json.md) + + + + +[Result query telemetry for filtered and ordered entities](/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.graphql.json.md) + + + + +[Result query telemetry for filtered and ordered entities](/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.rest.json.md) + + + + \ No newline at end of file diff --git a/evita_functional_tests/src/test/java/io/evitadb/api/query/descriptor/ConstraintDescriptorProviderTest.java b/evita_functional_tests/src/test/java/io/evitadb/api/query/descriptor/ConstraintDescriptorProviderTest.java index de766cd40..577147ee5 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/api/query/descriptor/ConstraintDescriptorProviderTest.java +++ b/evita_functional_tests/src/test/java/io/evitadb/api/query/descriptor/ConstraintDescriptorProviderTest.java @@ -221,7 +221,8 @@ void shouldFindAllConstraintForSpecificTypeAndPropertyTypeAndSupportedValue() { @ConstraintDefinition( name = "something", - shortDescription = "This is a constraint." + shortDescription = "This is a constraint.", + userDocsLink = "link" ) private static class UnknownConstraint extends ConstraintLeaf implements FilterConstraint, AttributeConstraint { diff --git a/evita_functional_tests/src/test/java/io/evitadb/api/query/descriptor/ConstraintDescriptorTest.java b/evita_functional_tests/src/test/java/io/evitadb/api/query/descriptor/ConstraintDescriptorTest.java index 682bbba7f..8bcd39583 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/api/query/descriptor/ConstraintDescriptorTest.java +++ b/evita_functional_tests/src/test/java/io/evitadb/api/query/descriptor/ConstraintDescriptorTest.java @@ -166,6 +166,7 @@ private static ConstraintDescriptor createComparableDescriptor(@Nonnull Constrai propertyType, name, "This is a description.", + "link", Set.of(ConstraintDomain.ENTITY), null, creator @@ -200,6 +201,7 @@ private ConstraintDescriptor createBaseFilterDescriptor(@Nonnull ConstraintPrope propertyType, name, "This is a description.", + "link", Set.of(ConstraintDomain.ENTITY), null, creator diff --git a/evita_functional_tests/src/test/java/io/evitadb/api/query/descriptor/ConstraintProcessorTest.java b/evita_functional_tests/src/test/java/io/evitadb/api/query/descriptor/ConstraintProcessorTest.java index a63c5036d..6a989753b 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/api/query/descriptor/ConstraintProcessorTest.java +++ b/evita_functional_tests/src/test/java/io/evitadb/api/query/descriptor/ConstraintProcessorTest.java @@ -153,6 +153,7 @@ private static ConstraintDescriptor createConstraintWithConstructorAndFactoryMet ConstraintPropertyType.ATTRIBUTE, "something", "This is a constraint.", + "link", Set.of(ConstraintDomain.GENERIC), null, new ConstraintCreator( @@ -178,6 +179,7 @@ private static ConstraintDescriptor createConstraintWithConstructorAndFactoryMet ConstraintPropertyType.ATTRIBUTE, "somethingDefault", "This is a constraint.", + "link", Set.of(ConstraintDomain.GENERIC), null, new ConstraintCreator( @@ -196,6 +198,7 @@ private static ConstraintDescriptor createAndDescriptor() { ConstraintPropertyType.GENERIC, "and", "This is a constraint.", + "link", Set.of(ConstraintDomain.ENTITY, ConstraintDomain.REFERENCE), null, new ConstraintCreator( @@ -224,6 +227,7 @@ private static ConstraintDescriptor createAttributeStartsWithDescriptor() { ConstraintPropertyType.ATTRIBUTE, "startsWith", "This is a constraint.", + "link", Set.of(ConstraintDomain.ENTITY, ConstraintDomain.REFERENCE), new SupportedValues( Set.of(String.class), @@ -253,6 +257,7 @@ private static ConstraintDescriptor createHierarchyWithinDescriptor() { ConstraintPropertyType.HIERARCHY, "within", "This is a constraint.", + "link", Set.of(ConstraintDomain.ENTITY), null, new ConstraintCreator( @@ -288,6 +293,7 @@ private static ConstraintDescriptor createHierarchyWithinSelfDescriptor() { ConstraintPropertyType.HIERARCHY, "withinSelf", "This is a constraint.", + "link", Set.of(ConstraintDomain.ENTITY), null, new ConstraintCreator( @@ -325,7 +331,8 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", - shortDescription = "This is a constraint." + shortDescription = "This is a constraint.", + userDocsLink = "link" ) private static class ConstraintWithoutCreator extends AbstractAttributeFilterConstraintLeaf { @@ -341,7 +348,8 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", - shortDescription = "This is a constraint." + shortDescription = "This is a constraint.", + userDocsLink = "link" ) private static class ConstraintWithoutType implements Constraint { @@ -388,7 +396,8 @@ public ConstraintWithoutType cloneWithArguments(@Nonnull Serializable[] newArgum @ConstraintDefinition( name = "something", - shortDescription = "This is a constraint." + shortDescription = "This is a constraint.", + userDocsLink = "link" ) private static class ConstraintWithoutPropertyType implements FilterConstraint { @@ -435,7 +444,8 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", - shortDescription = "This is a constraint." + shortDescription = "This is a constraint.", + userDocsLink = "link" ) private static class ConstraintWithConstructorAndFactoryMethod extends AbstractAttributeFilterConstraintLeaf { @@ -458,7 +468,8 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", - shortDescription = "This is a constraint." + shortDescription = "This is a constraint.", + userDocsLink = "link" ) private static class SimilarConstraintA extends AbstractAttributeFilterConstraintLeaf { @@ -475,7 +486,8 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", - shortDescription = "This is a constraint." + shortDescription = "This is a constraint.", + userDocsLink = "link" ) private static class SimilarConstraintB extends AbstractAttributeFilterConstraintLeaf { @@ -492,7 +504,8 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", - shortDescription = "This is a constraint." + shortDescription = "This is a constraint.", + userDocsLink = "link" ) private static class SimilarConstraintWithSuffixedCreatorsA extends AbstractAttributeFilterConstraintLeaf { @@ -509,7 +522,8 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", - shortDescription = "This is a constraint." + shortDescription = "This is a constraint.", + userDocsLink = "link" ) private static class SimilarConstraintWithSuffixedCreatorsB extends AbstractAttributeFilterConstraintLeaf { @@ -526,7 +540,8 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", - shortDescription = "This is a constraint." + shortDescription = "This is a constraint.", + userDocsLink = "link" ) private static class ConstraintWithSameConditionWithoutClassifier extends AbstractAttributeFilterConstraintLeaf { @@ -543,7 +558,8 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", - shortDescription = "This is a constraint." + shortDescription = "This is a constraint.", + userDocsLink = "link" ) private static class ConstraintWithSameConditionWithClassifier extends AbstractAttributeFilterConstraintLeaf { @@ -560,7 +576,8 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", - shortDescription = "This is a constraint." + shortDescription = "This is a constraint.", + userDocsLink = "link" ) private static class ConstraintWithSameConditionWithAndWithoutClassifier extends AbstractAttributeFilterConstraintLeaf { @@ -582,7 +599,8 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", - shortDescription = "This is a constraint." + shortDescription = "This is a constraint.", + userDocsLink = "link" ) private static class ConstraintWithSameConditionAndDifferentFullNamesOrClassifiersA extends AbstractAttributeFilterConstraintLeaf { @@ -599,7 +617,8 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", - shortDescription = "This is a constraint." + shortDescription = "This is a constraint.", + userDocsLink = "link" ) private static class ConstraintWithSameConditionAndDifferentFullNamesOrClassifiersB extends AbstractAttributeFilterConstraintLeaf { @@ -620,7 +639,8 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", - shortDescription = "This is a constraint." + shortDescription = "This is a constraint.", + userDocsLink = "link" ) private static class ConstraintWithDuplicateCreators extends AbstractAttributeFilterConstraintLeaf { @@ -641,7 +661,8 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", - shortDescription = "This is a constraint." + shortDescription = "This is a constraint.", + userDocsLink = "link" ) private static class ConstraintAWithoutSuffix extends AbstractAttributeFilterConstraintLeaf { @@ -658,7 +679,8 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "somethingElse", - shortDescription = "This is a constraint." + shortDescription = "This is a constraint.", + userDocsLink = "link" ) private static class ConstraintBWithSuffix extends AbstractAttributeFilterConstraintLeaf { @@ -675,7 +697,8 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", - shortDescription = "This is a constraint." + shortDescription = "This is a constraint.", + userDocsLink = "link" ) private static class ConstraintWithMultipleImplicitClassifiers extends AbstractAttributeFilterConstraintLeaf { @@ -692,7 +715,8 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", - shortDescription = "This is a constraint." + shortDescription = "This is a constraint.", + userDocsLink = "link" ) private static class ConstraintWithMultipleAdditionalChildren extends AbstractAttributeFilterConstraintContainer { @@ -704,7 +728,8 @@ public ConstraintWithMultipleAdditionalChildren(@Nonnull OrderBy orderBy, @ConstraintDefinition( name = "something", - shortDescription = "This is a constraint." + shortDescription = "This is a constraint.", + userDocsLink = "link" ) private static class ConstraintWithMultipleSameAdditionalChildren extends AbstractAttributeFilterConstraintLeaf { diff --git a/evita_functional_tests/src/test/java/io/evitadb/documentation/JsonExecutable.java b/evita_functional_tests/src/test/java/io/evitadb/documentation/JsonExecutable.java index a8633fecd..d63d1f27f 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/documentation/JsonExecutable.java +++ b/evita_functional_tests/src/test/java/io/evitadb/documentation/JsonExecutable.java @@ -40,6 +40,7 @@ import io.evitadb.api.requestResponse.extraResult.Histogram; import io.evitadb.api.requestResponse.extraResult.HistogramContract.Bucket; import io.evitadb.api.requestResponse.extraResult.PriceHistogram; +import io.evitadb.api.requestResponse.extraResult.QueryTelemetry; import io.evitadb.dataType.PaginatedList; import io.evitadb.dataType.StripList; import io.evitadb.documentation.evitaql.CustomJsonVisibilityChecker; @@ -75,7 +76,8 @@ public abstract class JsonExecutable { allow(AttributeHistogram.class), allow(PriceHistogram.class), allow(Bucket.class), - allow(StripList.class) + allow(StripList.class), + allow(QueryTelemetry.class) ) ); OBJECT_MAPPER.registerModule(new Jdk8Module()); diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/QueryTelemetry.java b/evita_query/src/main/java/io/evitadb/api/query/require/QueryTelemetry.java index 8d6534ab3..decfa4dc4 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/QueryTelemetry.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/QueryTelemetry.java @@ -49,7 +49,7 @@ shortDescription = "The constraint triggers computation of query telemetry (explaining what operations were performed and how long they took) in extra results of the response.", userDocsLink = "/documentation/query/requirements/debug#query-telemetry" ) -public class QueryTelemetry extends AbstractRequireConstraintLeaf implements GenericConstraint { +public class QueryTelemetry extends AbstractRequireConstraintLeaf implements GenericConstraint, ExtraResultRequireConstraint { @Serial private static final long serialVersionUID = -5121347556508500340L; private QueryTelemetry(Serializable... arguments) { From bdcacf31a0c82a09731e6954f68d53d5bfd6d145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Fri, 8 Dec 2023 12:14:52 +0100 Subject: [PATCH 05/52] fix: query telemetry deserialization and docs --- .../telemetry/queryTelemetry.evitaql.json.md | 71 +++++++++++++++++++ .../telemetry/queryTelemetry.graphql.json.md | 40 +++++------ .../telemetry/queryTelemetry.rest.json.md | 40 +++++------ .../extraResult/QueryTelemetry.java | 2 +- .../documentation/UserDocumentationTest.java | 4 +- .../api/query/require/QueryTelemetry.java | 2 +- 6 files changed, 115 insertions(+), 44 deletions(-) create mode 100644 documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.evitaql.json.md diff --git a/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.evitaql.json.md b/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.evitaql.json.md new file mode 100644 index 000000000..1208f9eae --- /dev/null +++ b/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.evitaql.json.md @@ -0,0 +1,71 @@ +```json +{ + "operation": "OVERALL", + "spentTime": 16600528, + "start": 182789468201165, + "steps": [ + { + "operation": "PLANNING", + "spentTime": 5364071, + "start": 182789468283318, + "steps": [ + { + "operation": "PLANNING_INDEX_USAGE", + "spentTime": 122980, + "start": 182789468289249 + }, + { + "arguments": [ + "Selected index: Index type: GLOBAL, estimated costs 585" + ], + "operation": "PLANNING_FILTER", + "spentTime": 4078102, + "start": 182789468487099, + "steps": [ + { + "arguments": [ + "Index type: GLOBAL, estimated costs 585" + ], + "operation": "PLANNING_FILTER_ALTERNATIVE", + "spentTime": 4022569, + "start": 182789468497849 + } + ] + }, + { + "operation": "PLANNING_SORT", + "spentTime": 886673, + "start": 182789472570591 + }, + { + "operation": "PLANNING_EXTRA_RESULT_FABRICATION", + "spentTime": 155420, + "start": 182789473465750 + } + ] + }, + { + "operation": "EXECUTION", + "spentTime": 11145588, + "start": 182789473655695, + "steps": [ + { + "operation": "EXECUTION_FILTER", + "spentTime": 224608, + "start": 182789473704034 + }, + { + "operation": "EXECUTION_SORT_AND_SLICE", + "spentTime": 778762, + "start": 182789473928893 + }, + { + "operation": "FETCHING", + "spentTime": 10044736, + "start": 182789474755324 + } + ] + } + ] +} +``` \ No newline at end of file diff --git a/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.graphql.json.md b/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.graphql.json.md index d023e09b7..2dbf7b3d3 100644 --- a/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.graphql.json.md +++ b/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.graphql.json.md @@ -1,80 +1,80 @@ ```json { "operation": "OVERALL", - "start": 177425818030395, + "start": 182792910774973, "steps": [ { "operation": "PLANNING", - "start": 177425818039060, + "start": 182792910785703, "steps": [ { "operation": "PLANNING_INDEX_USAGE", - "start": 177425818040553, + "start": 182792910786665, "steps": [ ], "arguments": [ ], - "spentTime": "10129" + "spentTime": "13845" }, { "operation": "PLANNING_FILTER", - "start": 177425818096047, + "start": 182792910807253, "steps": [ { "operation": "PLANNING_FILTER_ALTERNATIVE", - "start": 177425818101938, + "start": 182792910811050, "steps": [ ], "arguments": [ "Index type: GLOBAL, estimated costs 585" ], - "spentTime": "953217" + "spentTime": "1081806" } ], "arguments": [ "Selected index: Index type: GLOBAL, estimated costs 585" ], - "spentTime": "969337" + "spentTime": "1097987" }, { "operation": "PLANNING_SORT", - "start": 177425819073169, + "start": 182792911914768, "steps": [ ], "arguments": [ ], - "spentTime": "184534" + "spentTime": "198009" }, { "operation": "PLANNING_EXTRA_RESULT_FABRICATION", - "start": 177425819259727, + "start": 182792912113819, "steps": [ ], "arguments": [ ], - "spentTime": "31859" + "spentTime": "11440" } ], "arguments": [ ], - "spentTime": "1256193" + "spentTime": "1343494" }, { "operation": "EXECUTION", - "start": 177425819296625, + "start": 182792912130589, "steps": [ { "operation": "EXECUTION_FILTER", - "start": 177425819301374, + "start": 182792912135459, "steps": [ ], "arguments": [ ], - "spentTime": "45776" + "spentTime": "53931" }, { "operation": "EXECUTION_SORT_AND_SLICE", - "start": 177425819347701, + "start": 182792912189580, "steps": [ ], "arguments": [ ], - "spentTime": "500843" + "spentTime": "149889" } ], "arguments": [ ], - "spentTime": "616930" + "spentTime": "255648" } ], "arguments": [ ], - "spentTime": "1883812" + "spentTime": "1611755" } ``` \ No newline at end of file diff --git a/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.rest.json.md b/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.rest.json.md index f3c2f1204..97e9b557c 100644 --- a/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.rest.json.md +++ b/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.rest.json.md @@ -1,80 +1,80 @@ ```json { "operation": "OVERALL", - "start": 177425764174411, + "start": 182792818627703, "steps": [ { "operation": "PLANNING", - "start": 177425764186033, + "start": 182792818636219, "steps": [ { "operation": "PLANNING_INDEX_USAGE", - "start": 177425764188768, + "start": 182792818637391, "steps": [ ], "arguments": [ ], - "spentTime": "0.000012364s" + "spentTime": "0.000010700s" }, { "operation": "PLANNING_FILTER", - "start": 177425764211030, + "start": 182792818660134, "steps": [ { "operation": "PLANNING_FILTER_ALTERNATIVE", - "start": 177425764217070, + "start": 182792818663169, "steps": [ ], "arguments": [ "Index type: GLOBAL, estimated costs 585" ], - "spentTime": "0.000986590s" + "spentTime": "0.000884458s" } ], "arguments": [ "Selected index: Index type: GLOBAL, estimated costs 585" ], - "spentTime": "0.001002448s" + "spentTime": "0.000895418s" }, { "operation": "PLANNING_SORT", - "start": 177425765215422, + "start": 182792819556774, "steps": [ ], "arguments": [ ], - "spentTime": "0.000113883s" + "spentTime": "0.000109916s" }, { "operation": "PLANNING_EXTRA_RESULT_FABRICATION", - "start": 177425765330918, + "start": 182792819667421, "steps": [ ], "arguments": [ ], - "spentTime": "0.000009688s" + "spentTime": "0.000008095s" } ], "arguments": [ ], - "spentTime": "0.001157799s" + "spentTime": "0.001042102s" }, { "operation": "EXECUTION", - "start": 177425765381451, + "start": 182792819679143, "steps": [ { "operation": "EXECUTION_FILTER", - "start": 177425765386741, + "start": 182792819686767, "steps": [ ], "arguments": [ ], - "spentTime": "0.000043041s" + "spentTime": "0.000039393s" }, { "operation": "EXECUTION_SORT_AND_SLICE", - "start": 177425765430594, + "start": 182792819726230, "steps": [ ], "arguments": [ ], - "spentTime": "0.000157423s" + "spentTime": "0.000127649s" } ], "arguments": [ ], - "spentTime": "0.000262340s" + "spentTime": "0.000273029s" } ], "arguments": [ ], - "spentTime": "0.001470131s" + "spentTime": "0.001324970s" } ``` \ No newline at end of file diff --git a/evita_api/src/main/java/io/evitadb/api/requestResponse/extraResult/QueryTelemetry.java b/evita_api/src/main/java/io/evitadb/api/requestResponse/extraResult/QueryTelemetry.java index 05c24c602..7750d8a4c 100644 --- a/evita_api/src/main/java/io/evitadb/api/requestResponse/extraResult/QueryTelemetry.java +++ b/evita_api/src/main/java/io/evitadb/api/requestResponse/extraResult/QueryTelemetry.java @@ -87,7 +87,7 @@ public QueryTelemetry(@Nonnull QueryPhase operation, long start, long spentTime, this.spentTime = spentTime; this.arguments = arguments; for (QueryTelemetry step : steps) { - step.addStep(step); + addStep(step); } } 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 afd3e8929..64d0d09bc 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 @@ -376,9 +376,9 @@ Stream testSingleFileDocumentation() { Stream testSingleFileDocumentationAndCreateOtherLanguageSnippets() { return this.createTests( DocumentationProfile.DEFAULT, - getRootDirectory().resolve("documentation/user/en/query/requirements/fetching.md"), + getRootDirectory().resolve("documentation/user/en/query/requirements/telemetry.md"), ExampleFilter.values(), - CreateSnippets.MARKDOWN, CreateSnippets.JAVA, CreateSnippets.GRAPHQL, CreateSnippets.REST, CreateSnippets.CSHARP + CreateSnippets.MARKDOWN/*, CreateSnippets.JAVA*//*, CreateSnippets.GRAPHQL, CreateSnippets.REST, CreateSnippets.CSHARP*/ ).stream(); } diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/QueryTelemetry.java b/evita_query/src/main/java/io/evitadb/api/query/require/QueryTelemetry.java index decfa4dc4..8d6534ab3 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/QueryTelemetry.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/QueryTelemetry.java @@ -49,7 +49,7 @@ shortDescription = "The constraint triggers computation of query telemetry (explaining what operations were performed and how long they took) in extra results of the response.", userDocsLink = "/documentation/query/requirements/debug#query-telemetry" ) -public class QueryTelemetry extends AbstractRequireConstraintLeaf implements GenericConstraint, ExtraResultRequireConstraint { +public class QueryTelemetry extends AbstractRequireConstraintLeaf implements GenericConstraint { @Serial private static final long serialVersionUID = -5121347556508500340L; private QueryTelemetry(Serializable... arguments) { From a9b82d89f5093657269d34021ee275182651432c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Fri, 8 Dec 2023 12:15:03 +0100 Subject: [PATCH 06/52] fix: query telemetry deserialization and docs --- .../test/client/query/graphql/GraphQLQueryConverter.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 b9e67dee7..f09493b00 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 @@ -171,11 +171,12 @@ private String convertOutputFields(@Nonnull CatalogSchemaContract catalogSchema, final Page page = QueryUtils.findConstraint(require, Page.class, SeparateEntityContentRequireContainer.class); final Strip strip = QueryUtils.findConstraint(require, Strip.class, SeparateEntityContentRequireContainer.class); final List> extraResultConstraints = QueryUtils.findConstraints(require, c -> c instanceof ExtraResultRequireConstraint); + final QueryTelemetry queryTelemetry = QueryUtils.findConstraint(require, QueryTelemetry.class); recordsConverter.convert(fieldsBuilder, entityType, locale, entityFetch, page, strip, !extraResultConstraints.isEmpty()); // build extra results - if (!extraResultConstraints.isEmpty()) { + if (!extraResultConstraints.isEmpty() || queryTelemetry != null) { fieldsBuilder.addObjectField(ResponseDescriptor.EXTRA_RESULTS, extraResultsBuilder -> { facetSummaryConverter.convert( extraResultsBuilder, @@ -203,8 +204,8 @@ private String convertOutputFields(@Nonnull CatalogSchemaContract catalogSchema, Optional.ofNullable(QueryUtils.findConstraint(require, PriceHistogram.class)) .ifPresent(priceHistogram -> priceHistogramConverter.convert(extraResultsBuilder, priceHistogram)); - Optional.ofNullable(QueryUtils.findConstraint(require, QueryTelemetry.class)) - .ifPresent(queryTelemetry -> queryTelemetryConverter.convert(extraResultsBuilder, queryTelemetry)); + Optional.ofNullable(queryTelemetry) + .ifPresent(it -> queryTelemetryConverter.convert(extraResultsBuilder, it)); }); } } From 27b0583390ad95ca3a181423200ee4bb5d326b3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Fri, 8 Dec 2023 12:17:10 +0100 Subject: [PATCH 07/52] docs: make sure that query telemetry examples do pass (they are different for each call) --- ...evitaql.json.md => queryTelemetryResult.evitaql.json.md} | 0 ...graphql.json.md => queryTelemetryResult.graphql.json.md} | 0 ...metry.rest.json.md => queryTelemetryResult.rest.json.md} | 0 documentation/user/en/query/requirements/telemetry.md | 6 +++--- 4 files changed, 3 insertions(+), 3 deletions(-) rename documentation/user/en/query/requirements/examples/telemetry/{queryTelemetry.evitaql.json.md => queryTelemetryResult.evitaql.json.md} (100%) rename documentation/user/en/query/requirements/examples/telemetry/{queryTelemetry.graphql.json.md => queryTelemetryResult.graphql.json.md} (100%) rename documentation/user/en/query/requirements/examples/telemetry/{queryTelemetry.rest.json.md => queryTelemetryResult.rest.json.md} (100%) diff --git a/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.evitaql.json.md b/documentation/user/en/query/requirements/examples/telemetry/queryTelemetryResult.evitaql.json.md similarity index 100% rename from documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.evitaql.json.md rename to documentation/user/en/query/requirements/examples/telemetry/queryTelemetryResult.evitaql.json.md diff --git a/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.graphql.json.md b/documentation/user/en/query/requirements/examples/telemetry/queryTelemetryResult.graphql.json.md similarity index 100% rename from documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.graphql.json.md rename to documentation/user/en/query/requirements/examples/telemetry/queryTelemetryResult.graphql.json.md diff --git a/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.rest.json.md b/documentation/user/en/query/requirements/examples/telemetry/queryTelemetryResult.rest.json.md similarity index 100% rename from documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.rest.json.md rename to documentation/user/en/query/requirements/examples/telemetry/queryTelemetryResult.rest.json.md diff --git a/documentation/user/en/query/requirements/telemetry.md b/documentation/user/en/query/requirements/telemetry.md index 96c614ed5..7fb82dc70 100644 --- a/documentation/user/en/query/requirements/telemetry.md +++ b/documentation/user/en/query/requirements/telemetry.md @@ -75,17 +75,17 @@ The result contains query telemetry and some products (which we omitted here for -[Result query telemetry for filtered and ordered entities](/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.evitaql.json.md) +[Result query telemetry for filtered and ordered entities](/documentation/user/en/query/requirements/examples/telemetry/queryTelemetryResult.evitaql.json.md) -[Result query telemetry for filtered and ordered entities](/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.graphql.json.md) +[Result query telemetry for filtered and ordered entities](/documentation/user/en/query/requirements/examples/telemetry/queryTelemetryResult.graphql.json.md) -[Result query telemetry for filtered and ordered entities](/documentation/user/en/query/requirements/examples/telemetry/queryTelemetry.rest.json.md) +[Result query telemetry for filtered and ordered entities](/documentation/user/en/query/requirements/examples/telemetry/queryTelemetryResult.rest.json.md) From 6cb99f9f9e0b5218aee9159fb9ace7fb2bc0e14c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Fri, 8 Dec 2023 12:20:40 +0100 Subject: [PATCH 08/52] docs: query telemetry fixes --- documentation/user/en/query/requirements/telemetry.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/documentation/user/en/query/requirements/telemetry.md b/documentation/user/en/query/requirements/telemetry.md index 7fb82dc70..91fd088e8 100644 --- a/documentation/user/en/query/requirements/telemetry.md +++ b/documentation/user/en/query/requirements/telemetry.md @@ -12,10 +12,14 @@ preferredLang: 'evitaql' ## Query telemetry + + ```evitaql-syntax queryTelemetry() ``` + + The evita_query/src/main/java/io/evitadb/api/query/require/QueryTelemetry.java requirement EvitaDB.Client/Queries/Requires/QueryTelemetry.cs requirement `queryTelemetry` extra result field @@ -29,8 +33,7 @@ the following data:
operation
Phase of the query execution. - Possible values can be found in the evita_api/src/main/java/io/evitadb/api/requestResponse/extraResult/QueryTelemetry.java class - EvitaDB.Client/Models/ExtraResults/QueryTelemetry.cs class. + Possible values can be found in the evita_api/src/main/java/io/evitadb/api/requestResponse/extraResult/QueryTelemetry.java classEvitaDB.Client/Models/ExtraResults/QueryTelemetry.cs class.
start
@@ -55,6 +58,7 @@ By default the number values of the telemetry object are returned in raw form. Y using argument `format` on the `queryTelemetry` field. This way human-readable values are returned. + To demonstrate the information the query telemetry is providing, we will use the following query that filters and sorts entities: From 63c81399ac7f608de87adab264b30230a81b0515 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Fri, 8 Dec 2023 12:43:14 +0100 Subject: [PATCH 09/52] fix: test constraint docs links --- .../ConstraintDescriptorProviderTest.java | 2 +- .../descriptor/ConstraintDescriptorTest.java | 4 +- .../descriptor/ConstraintProcessorTest.java | 50 +++++++++---------- .../evitadb/api/query/QueryConstraints.java | 14 +++--- 4 files changed, 35 insertions(+), 35 deletions(-) diff --git a/evita_functional_tests/src/test/java/io/evitadb/api/query/descriptor/ConstraintDescriptorProviderTest.java b/evita_functional_tests/src/test/java/io/evitadb/api/query/descriptor/ConstraintDescriptorProviderTest.java index 577147ee5..f24234e06 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/api/query/descriptor/ConstraintDescriptorProviderTest.java +++ b/evita_functional_tests/src/test/java/io/evitadb/api/query/descriptor/ConstraintDescriptorProviderTest.java @@ -222,7 +222,7 @@ void shouldFindAllConstraintForSpecificTypeAndPropertyTypeAndSupportedValue() { @ConstraintDefinition( name = "something", shortDescription = "This is a constraint.", - userDocsLink = "link" + userDocsLink = "/link" ) private static class UnknownConstraint extends ConstraintLeaf implements FilterConstraint, AttributeConstraint { diff --git a/evita_functional_tests/src/test/java/io/evitadb/api/query/descriptor/ConstraintDescriptorTest.java b/evita_functional_tests/src/test/java/io/evitadb/api/query/descriptor/ConstraintDescriptorTest.java index 8bcd39583..bd68b0d13 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/api/query/descriptor/ConstraintDescriptorTest.java +++ b/evita_functional_tests/src/test/java/io/evitadb/api/query/descriptor/ConstraintDescriptorTest.java @@ -166,7 +166,7 @@ private static ConstraintDescriptor createComparableDescriptor(@Nonnull Constrai propertyType, name, "This is a description.", - "link", + "/link", Set.of(ConstraintDomain.ENTITY), null, creator @@ -201,7 +201,7 @@ private ConstraintDescriptor createBaseFilterDescriptor(@Nonnull ConstraintPrope propertyType, name, "This is a description.", - "link", + "/link", Set.of(ConstraintDomain.ENTITY), null, creator diff --git a/evita_functional_tests/src/test/java/io/evitadb/api/query/descriptor/ConstraintProcessorTest.java b/evita_functional_tests/src/test/java/io/evitadb/api/query/descriptor/ConstraintProcessorTest.java index 6a989753b..6ed8fb439 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/api/query/descriptor/ConstraintProcessorTest.java +++ b/evita_functional_tests/src/test/java/io/evitadb/api/query/descriptor/ConstraintProcessorTest.java @@ -153,7 +153,7 @@ private static ConstraintDescriptor createConstraintWithConstructorAndFactoryMet ConstraintPropertyType.ATTRIBUTE, "something", "This is a constraint.", - "link", + "/link", Set.of(ConstraintDomain.GENERIC), null, new ConstraintCreator( @@ -179,7 +179,7 @@ private static ConstraintDescriptor createConstraintWithConstructorAndFactoryMet ConstraintPropertyType.ATTRIBUTE, "somethingDefault", "This is a constraint.", - "link", + "/link", Set.of(ConstraintDomain.GENERIC), null, new ConstraintCreator( @@ -198,7 +198,7 @@ private static ConstraintDescriptor createAndDescriptor() { ConstraintPropertyType.GENERIC, "and", "This is a constraint.", - "link", + "/link", Set.of(ConstraintDomain.ENTITY, ConstraintDomain.REFERENCE), null, new ConstraintCreator( @@ -227,7 +227,7 @@ private static ConstraintDescriptor createAttributeStartsWithDescriptor() { ConstraintPropertyType.ATTRIBUTE, "startsWith", "This is a constraint.", - "link", + "/link", Set.of(ConstraintDomain.ENTITY, ConstraintDomain.REFERENCE), new SupportedValues( Set.of(String.class), @@ -257,7 +257,7 @@ private static ConstraintDescriptor createHierarchyWithinDescriptor() { ConstraintPropertyType.HIERARCHY, "within", "This is a constraint.", - "link", + "/link", Set.of(ConstraintDomain.ENTITY), null, new ConstraintCreator( @@ -293,7 +293,7 @@ private static ConstraintDescriptor createHierarchyWithinSelfDescriptor() { ConstraintPropertyType.HIERARCHY, "withinSelf", "This is a constraint.", - "link", + "/link", Set.of(ConstraintDomain.ENTITY), null, new ConstraintCreator( @@ -332,7 +332,7 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", shortDescription = "This is a constraint.", - userDocsLink = "link" + userDocsLink = "/link" ) private static class ConstraintWithoutCreator extends AbstractAttributeFilterConstraintLeaf { @@ -349,7 +349,7 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", shortDescription = "This is a constraint.", - userDocsLink = "link" + userDocsLink = "/link" ) private static class ConstraintWithoutType implements Constraint { @@ -397,7 +397,7 @@ public ConstraintWithoutType cloneWithArguments(@Nonnull Serializable[] newArgum @ConstraintDefinition( name = "something", shortDescription = "This is a constraint.", - userDocsLink = "link" + userDocsLink = "/link" ) private static class ConstraintWithoutPropertyType implements FilterConstraint { @@ -445,7 +445,7 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", shortDescription = "This is a constraint.", - userDocsLink = "link" + userDocsLink = "/link" ) private static class ConstraintWithConstructorAndFactoryMethod extends AbstractAttributeFilterConstraintLeaf { @@ -469,7 +469,7 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", shortDescription = "This is a constraint.", - userDocsLink = "link" + userDocsLink = "/link" ) private static class SimilarConstraintA extends AbstractAttributeFilterConstraintLeaf { @@ -487,7 +487,7 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", shortDescription = "This is a constraint.", - userDocsLink = "link" + userDocsLink = "/link" ) private static class SimilarConstraintB extends AbstractAttributeFilterConstraintLeaf { @@ -505,7 +505,7 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", shortDescription = "This is a constraint.", - userDocsLink = "link" + userDocsLink = "/link" ) private static class SimilarConstraintWithSuffixedCreatorsA extends AbstractAttributeFilterConstraintLeaf { @@ -523,7 +523,7 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", shortDescription = "This is a constraint.", - userDocsLink = "link" + userDocsLink = "/link" ) private static class SimilarConstraintWithSuffixedCreatorsB extends AbstractAttributeFilterConstraintLeaf { @@ -541,7 +541,7 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", shortDescription = "This is a constraint.", - userDocsLink = "link" + userDocsLink = "/link" ) private static class ConstraintWithSameConditionWithoutClassifier extends AbstractAttributeFilterConstraintLeaf { @@ -559,7 +559,7 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", shortDescription = "This is a constraint.", - userDocsLink = "link" + userDocsLink = "/link" ) private static class ConstraintWithSameConditionWithClassifier extends AbstractAttributeFilterConstraintLeaf { @@ -577,7 +577,7 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", shortDescription = "This is a constraint.", - userDocsLink = "link" + userDocsLink = "/link" ) private static class ConstraintWithSameConditionWithAndWithoutClassifier extends AbstractAttributeFilterConstraintLeaf { @@ -600,7 +600,7 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", shortDescription = "This is a constraint.", - userDocsLink = "link" + userDocsLink = "/link" ) private static class ConstraintWithSameConditionAndDifferentFullNamesOrClassifiersA extends AbstractAttributeFilterConstraintLeaf { @@ -618,7 +618,7 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", shortDescription = "This is a constraint.", - userDocsLink = "link" + userDocsLink = "/link" ) private static class ConstraintWithSameConditionAndDifferentFullNamesOrClassifiersB extends AbstractAttributeFilterConstraintLeaf { @@ -640,7 +640,7 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", shortDescription = "This is a constraint.", - userDocsLink = "link" + userDocsLink = "/link" ) private static class ConstraintWithDuplicateCreators extends AbstractAttributeFilterConstraintLeaf { @@ -662,7 +662,7 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", shortDescription = "This is a constraint.", - userDocsLink = "link" + userDocsLink = "/link" ) private static class ConstraintAWithoutSuffix extends AbstractAttributeFilterConstraintLeaf { @@ -680,7 +680,7 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "somethingElse", shortDescription = "This is a constraint.", - userDocsLink = "link" + userDocsLink = "/link" ) private static class ConstraintBWithSuffix extends AbstractAttributeFilterConstraintLeaf { @@ -698,7 +698,7 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", shortDescription = "This is a constraint.", - userDocsLink = "link" + userDocsLink = "/link" ) private static class ConstraintWithMultipleImplicitClassifiers extends AbstractAttributeFilterConstraintLeaf { @@ -716,7 +716,7 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) @ConstraintDefinition( name = "something", shortDescription = "This is a constraint.", - userDocsLink = "link" + userDocsLink = "/link" ) private static class ConstraintWithMultipleAdditionalChildren extends AbstractAttributeFilterConstraintContainer { @@ -729,7 +729,7 @@ public ConstraintWithMultipleAdditionalChildren(@Nonnull OrderBy orderBy, @ConstraintDefinition( name = "something", shortDescription = "This is a constraint.", - userDocsLink = "link" + userDocsLink = "/link" ) private static class ConstraintWithMultipleSameAdditionalChildren extends AbstractAttributeFilterConstraintLeaf { diff --git a/evita_query/src/main/java/io/evitadb/api/query/QueryConstraints.java b/evita_query/src/main/java/io/evitadb/api/query/QueryConstraints.java index 05eb5168d..b50d05002 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/QueryConstraints.java +++ b/evita_query/src/main/java/io/evitadb/api/query/QueryConstraints.java @@ -1745,21 +1745,21 @@ static EntityPrimaryKeyInSet entityPrimaryKeyInSet(@Nullable int[] primaryKey) { */ /** - * This `orderBy` is container for ordering. It is mandatory container when any ordering is to be used. - * evitaDB requires a previously prepared sort index to be able to sort entities. This fact makes sorting much faster + * This `orderBy` is container for ordering. It is mandatory container when any ordering is to be used. + * evitaDB requires a previously prepared sort index to be able to sort entities. This fact makes sorting much faster * than ad-hoc sorting by attribute value. Also, the sorting mechanism of evitaDB is somewhat different from what you * might be used to. If you sort entities by two attributes in an orderBy clause of the query, evitaDB sorts them first - * by the first attribute (if present) and then by the second (but only those where the first attribute is missing). + * by the first attribute (if present) and then by the second (but only those where the first attribute is missing). * If two entities have the same value of the first attribute, they are not sorted by the second attribute, but by the - * primary key (in ascending order). If we want to use fast "pre-sorted" indexes, there is no other way to do it, + * primary key (in ascending order). If we want to use fast "pre-sorted" indexes, there is no other way to do it, * because the secondary order would not be known until a query time. * - * This default sorting behavior by multiple attributes is not always desirable, so evitaDB allows you to define + * This default sorting behavior by multiple attributes is not always desirable, so evitaDB allows you to define * a sortable attribute compound, which is a virtual attribute composed of the values of several other attributes. * evitaDB also allows you to specify the order of the "pre-sorting" behavior (ascending/descending) for each of these * attributes, and also the behavior for NULL values (first/last) if the attribute is completely missing in the entity. - * The sortable attribute compound is then used in the orderBy clause of the query instead of specifying the multiple - * individual attributes to achieve the expected sorting behavior while maintaining the speed of the "pre-sorted" + * The sortable attribute compound is then used in the orderBy clause of the query instead of specifying the multiple + * individual attributes to achieve the expected sorting behavior while maintaining the speed of the "pre-sorted" * indexes. * * Example: From 2a7861c02a33ce23cbda5c77b0a0bc30bd0f808d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Novotn=C3=BD?= Date: Fri, 8 Dec 2023 13:20:18 +0100 Subject: [PATCH 10/52] docs: updated documentation --- .../user/en/use/api/example/parent-class.java | 8 +- .../en/use/api/example/parent-interface.java | 12 ++- .../en/use/api/example/parent-record.java | 8 +- .../user/en/use/api/example/price-class.java | 32 +++++++ .../en/use/api/example/price-interface.java | 83 +++++++++++++++++++ .../user/en/use/api/example/price-record.java | 31 +++++++ documentation/user/en/use/api/query-data.md | 35 ++++++-- 7 files changed, 192 insertions(+), 17 deletions(-) create mode 100644 documentation/user/en/use/api/example/price-class.java create mode 100644 documentation/user/en/use/api/example/price-interface.java create mode 100644 documentation/user/en/use/api/example/price-record.java diff --git a/documentation/user/en/use/api/example/parent-class.java b/documentation/user/en/use/api/example/parent-class.java index 81e3d6a9d..0bc28e47a 100644 --- a/documentation/user/en/use/api/example/parent-class.java +++ b/documentation/user/en/use/api/example/parent-class.java @@ -3,18 +3,18 @@ public class MyEntity { // contains id of parent entity, or null if this entity is a root entity @ParentEntity - @Nullable private final Integer parentId(), + @Nullable private final Integer parentId, // contains parent entity, or null if this entity is a root entity @ParentEntity - @Nullable private final SealedEntity parentEntity(), + @Nullable private final SealedEntity parentEntity, // contains reference to a parent entity, or null if this entity is a root entity @ParentEntity - @Nullable private final EntityReference parentEntityReference(), + @Nullable private final EntityReference parentEntityReference, // contains reference to a parent wrapped in this interface, or null if this entity is a root entity @ParentEntity - @Nullable private final MyEntity parentMyEntity() + @Nullable private final MyEntity parentMyEntity } \ No newline at end of file diff --git a/documentation/user/en/use/api/example/parent-interface.java b/documentation/user/en/use/api/example/parent-interface.java index 9a58d281d..95266b5ff 100644 --- a/documentation/user/en/use/api/example/parent-interface.java +++ b/documentation/user/en/use/api/example/parent-interface.java @@ -1,32 +1,36 @@ public interface MyEntity { // return id of parent entity, or null if this entity is a root entity + // method throws ContextMissingException if the information about parent was not fetched from the server @ParentEntity - @Nullable Integer getParentId(); + @Nullable Integer getParentId() throws ContextMissingException; // return optional id of parent entity, or empty if this entity is a root entity @ParentEntity @Nonnull OptionalInt getParentIdIfNotRoot(); // return parent entity, or null if this entity is a root entity + // method throws ContextMissingException if the information about parent was not fetched from the server @ParentEntity - @Nullable SealedEntity getParentEntity(); + @Nullable SealedEntity getParentEntity() throws ContextMissingException; // return optional parent entity, or empty if this entity is a root entity @ParentEntity @Nonnull Optional getParentEntityIfNotRoot(); // return reference to a parent entity, or null if this entity is a root entity + // method throws ContextMissingException if the information about parent was not fetched from the server @ParentEntity - @Nullable EntityReference getParentEntityReference(); + @Nullable EntityReference getParentEntityReference() throws ContextMissingException; // return optional reference to a parent entity, or empty if this entity is a root entity @ParentEntity @Nonnull Optional getParentEntityReferenceIfPresent(); // return reference to a parent wrapped in this interface, or null if this entity is a root entity + // method throws ContextMissingException if the information about parent was not fetched from the server @ParentEntity - @Nullable MyEntity getParentMyEntity(); + @Nullable MyEntity getParentMyEntity() throws ContextMissingException; // return optional reference to a parent wrapped in this interface, or empty if this entity is a root entity @ParentEntity diff --git a/documentation/user/en/use/api/example/parent-record.java b/documentation/user/en/use/api/example/parent-record.java index 1b5f79d54..fd44df09d 100644 --- a/documentation/user/en/use/api/example/parent-record.java +++ b/documentation/user/en/use/api/example/parent-record.java @@ -1,18 +1,18 @@ public record MyEntity( // contains id of parent entity, or null if this entity is a root entity @ParentEntity - @Nullable Integer parentId(), + @Nullable Integer parentId, // contains parent entity, or null if this entity is a root entity @ParentEntity - @Nullable SealedEntity parentEntity(), + @Nullable SealedEntity parentEntity, // contains reference to a parent entity, or null if this entity is a root entity @ParentEntity - @Nullable EntityReference parentEntityReference(), + @Nullable EntityReference parentEntityReference, // contains reference to a parent wrapped in this interface, or null if this entity is a root entity @ParentEntity - @Nullable MyEntity parentMyEntity() + @Nullable MyEntity parentMyEntity ) { } \ No newline at end of file diff --git a/documentation/user/en/use/api/example/price-class.java b/documentation/user/en/use/api/example/price-class.java new file mode 100644 index 000000000..57cb5a0d9 --- /dev/null +++ b/documentation/user/en/use/api/example/price-class.java @@ -0,0 +1,32 @@ +@Data +public record MyEntity( + // contains the prices calculated as selling price for particular query + @PriceForSale + @Nullable private final PriceContract priceForSale, + + // contains all the prices that compete for being the selling price for particular entity + // from all those prices only the first one is used as selling price + @PriceForSaleRef + @Nullable private final PriceContract[] allPricesAvailableForSale, + + // contains price from the price list with name `basic` if it was fetched from the server + @Price(priceList = "basic") + @Nullable private final PriceContract basicPrice, + + // contains all prices of the entity that were fetched from the server + @Price + @NonNull private final Collection allPrices, + + // contains all prices of the entity that were fetched from the server as list + @Price + @NonNull private final List allPricesAsList, + + // contains all prices of the entity that were fetched from the server as set + @Price + @NonNull private final Set allPricesAsSet, + + // contains all prices of the entity that were fetched from the server as array + @Price + @Nullable private final PriceContract[] allPricesAsArray +) { +} \ No newline at end of file diff --git a/documentation/user/en/use/api/example/price-interface.java b/documentation/user/en/use/api/example/price-interface.java new file mode 100644 index 000000000..88e295a47 --- /dev/null +++ b/documentation/user/en/use/api/example/price-interface.java @@ -0,0 +1,83 @@ +public interface MyEntity { + + // returns the prices calculated as selling price for particular query + // throws ContextMissingException if the price information was not fetched from the server + @PriceForSale + @Nullable PriceContract getPriceForSale() throws ContextMissingException; + + // returns the prices calculated as selling price for particular query, + // or empty if the price information was not fetched from the server or not requested in the query + @PriceForSale + @NonNull Optional getPriceForSaleIfPresent(); + + // returns all the prices that compete for being the selling price for particular entity + // from all those prices only the first one is used as selling price + // throws ContextMissingException if the price information was not fetched from the server + @PriceForSaleRef + @Nullable PriceContract[] getAllPricesAvailableForSale() throws ContextMissingException; + + // returns first price from all the prices that compete for being the selling price for particular entity + // that match passed price list and currency, this price may not be used as selling price, because it doesn't + // take into account the price list priority that was used in the query + // throws ContextMissingException if the price information was not fetched from the server + @PriceForSale + @Nullable PriceContract getPriceAvailableForSale(@Nonnull String priceList, @Nonnull Currency currency) throws ContextMissingException; + + // returns first price from all the prices that compete for being the selling price for particular entity + // that match passed price list and currency and validity constraints, this price may not be used as selling price, + // because it doesn't take into account the price list priority that was used in the query + // throws ContextMissingException if the price information was not fetched from the server + @PriceForSale + @Nullable PriceContract getPriceAvailableForSale(@Nonnull String priceList, @Nonnull Currency currency, @Nonnull OffsetDateTime validNow) throws ContextMissingException; + + // returns all the prices that compete for being the selling price for particular entity that match passed price + // list, these prices may not be used as selling price, because they don't take into account the price list priority + // that was used in the query + // throws ContextMissingException if the price information was not fetched from the server + @PriceForSaleRef + @Nullable PriceContract[] getAllPricesAvailableForSale(@Nonnull String priceList) throws ContextMissingException; + + // returns all the prices that compete for being the selling price for particular entity that match passed currency, + // these prices may not be used as selling price, because they don't take into account the price list priority + // that was used in the query + // throws ContextMissingException if the price information was not fetched from the server + @PriceForSaleRef + @Nullable PriceContract[] getAllPricesAvailableForSale(@Nonnull Currency currency) throws ContextMissingException; + + // returns all the prices that compete for being the selling price for particular entity that match passed price + // list and currency, these prices may not be used as selling price, because they don't take into account the price + // list priority that was used in the query + // throws ContextMissingException if the price information was not fetched from the server + @PriceForSaleRef + @Nullable PriceContract[] getAllPricesAvailableForSale(@Nonnull String priceList, @Nonnull Currency currency) throws ContextMissingException; + + // returns price from the price list with name `basic` if it was fetched from the server + // throws ContextMissingException if the price information was not fetched from the server + @Price(priceList = "basic") + @Nullable PriceContract getBasicPrice() throws ContextMissingException; + + // returns price from the price list with name `basic` if it was fetched from the server, or empty value + @Price(priceList = "basic") + @NonNull Optional getBasicPriceIfPresent(); + + // returns all prices of the entity that were fetched from the server + // throws ContextMissingException if the price information was not fetched from the server + @Price + @NonNull Collection getAllPrices() throws ContextMissingException; + + // returns all prices of the entity that were fetched from the server as list + // throws ContextMissingException if the price information was not fetched from the server + @Price + @NonNull List getAllPricesAsList() throws ContextMissingException; + + // returns all prices of the entity that were fetched from the server as set + // throws ContextMissingException if the price information was not fetched from the server + @Price + @NonNull Set getAllPricesAsSet() throws ContextMissingException; + + // returns all prices of the entity that were fetched from the server as array + // throws ContextMissingException if the price information was not fetched from the server + @Price + @Nullable PriceContract[] getAllPricesAsArray() throws ContextMissingException; + +} \ No newline at end of file diff --git a/documentation/user/en/use/api/example/price-record.java b/documentation/user/en/use/api/example/price-record.java new file mode 100644 index 000000000..d383d064f --- /dev/null +++ b/documentation/user/en/use/api/example/price-record.java @@ -0,0 +1,31 @@ +public record MyEntity( + // contains the prices calculated as selling price for particular query + @PriceForSale + @Nullable PriceContract priceForSale, + + // contains all the prices that compete for being the selling price for particular entity + // from all those prices only the first one is used as selling price + @PriceForSaleRef + @Nullable PriceContract[] allPricesAvailableForSale, + + // contains price from the price list with name `basic` if it was fetched from the server + @Price(priceList = "basic") + @Nullable PriceContract basicPrice, + + // contains all prices of the entity that were fetched from the server + @Price + @NonNull Collection allPrices, + + // contains all prices of the entity that were fetched from the server as list + @Price + @NonNull List allPricesAsList, + + // contains all prices of the entity that were fetched from the server as set + @Price + @NonNull Set allPricesAsSet, + + // contains all prices of the entity that were fetched from the server as array + @Price + @Nullable PriceContract[] allPricesAsArray +) { +} \ No newline at end of file diff --git a/documentation/user/en/use/api/query-data.md b/documentation/user/en/use/api/query-data.md index 0ceb59638..bbf79c9f5 100644 --- a/documentation/user/en/use/api/query-data.md +++ b/documentation/user/en/use/api/query-data.md @@ -310,8 +310,10 @@ or `@PrimaryKeyRef` annotation: #### Attributes -To access the entity or reference attribute, you must use the appropriate data type and annotate it with the `@Attribute` -or `@AttributeRef` annotation. The data type can be wrapped in [Optional](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Optional.html) +To access the entity or reference attribute, you must use the appropriate data type and annotate it with the +evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/Attribute.java +or evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/AttributeRef.java +annotation. The data type can be wrapped in [Optional](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Optional.html) (or its counterparts [OptionalInt](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/OptionalInt.html) or [OptionalLong](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/OptionalLong.html)). @@ -343,7 +345,9 @@ call may fail with a `NullPointerException` if the data wasn't fetched even thou #### Associated data To access the entity or reference associated data, you must use the appropriate data type and annotate it with -the `@AssociatedData` or `@AssociatedDataRef` annotation. The data type can be wrapped in [Optional](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Optional.html) +the evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/AssociatedData.java +or evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/AssociatedDataRef.java +annotation. The data type can be wrapped in [Optional](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Optional.html) (or its counterparts [OptionalInt](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/OptionalInt.html) or [OptionalLong](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/OptionalLong.html)). @@ -362,14 +366,35 @@ from ["complex data type"](../data-types.md#complex-data-types) using [documente #### Prices -TODO JNO - Work in progress +To access the placement information of the entity prices, you must always work with +evita_api/src/main/java/io/evitadb/api/requestResponse/data/PriceContract.java data type +and annotate the methods with the evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/Price.java, +evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/PriceForSale.java or +evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/PriceForSaleRef.java +annotation. The datatype can be wrapped in +[Optional](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Optional.html) +(or its counterparts [OptionalInt](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/OptionalInt.html) +or [OptionalLong](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/OptionalLong.html)). + +If the method can return multiple prices, you need to wrap it in [Collection](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Collection.html) +(or its specializations [List](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/List.html) or [Set](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Set.html)). + + + +[Example interface with prices](/documentation/user/en/use/api/example/price-interface.java) + + + +The method may return null if the entity is a root entity. Therefore, it's not recommended to use primitive data types, +because the method call may fail with a `NullPointerException` in such a case. #### Hierarchy To access the placement information of the entity or hierarchy (i.e., its parent), you must use either the numeric data type, your own custom interface type, evita_api/src/main/java/io/evitadb/api/requestResponse/data/SealedEntity.java or evita_api/src/main/java/io/evitadb/api/requestResponse/data/structure/EntityReference.java -data type and annotate it with the `@ParentEntity` annotation. The datatype can be wrapped in +data type and annotate it with the evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/ParentEntity.java +annotation. The datatype can be wrapped in [Optional](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Optional.html) (or its counterparts [OptionalInt](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/OptionalInt.html) or [OptionalLong](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/OptionalLong.html)). From 70fbd9d94ae030c14f1e3532b663c16f1bab7913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Fri, 8 Dec 2023 15:06:48 +0100 Subject: [PATCH 11/52] docs: automatized external constraint user docs links in javadocs --- .../io/evitadb/documentation/JavaDocCopy.java | 63 +++++++++++++++++++ .../java/io/evitadb/api/query/filter/And.java | 4 +- .../api/query/filter/AttributeBetween.java | 4 +- .../api/query/filter/AttributeContains.java | 4 +- .../api/query/filter/AttributeEndsWith.java | 4 +- .../api/query/filter/AttributeEquals.java | 4 +- .../query/filter/AttributeGreaterThan.java | 4 +- .../filter/AttributeGreaterThanEquals.java | 4 +- .../api/query/filter/AttributeInRange.java | 4 +- .../api/query/filter/AttributeInSet.java | 4 +- .../evitadb/api/query/filter/AttributeIs.java | 4 +- .../api/query/filter/AttributeLessThan.java | 4 +- .../query/filter/AttributeLessThanEquals.java | 4 +- .../api/query/filter/AttributeStartsWith.java | 4 +- .../api/query/filter/EntityHaving.java | 2 + .../api/query/filter/EntityLocaleEquals.java | 4 +- .../query/filter/EntityPrimaryKeyInSet.java | 4 +- .../evitadb/api/query/filter/FacetHaving.java | 4 +- .../io/evitadb/api/query/filter/FilterBy.java | 4 +- .../api/query/filter/FilterGroupBy.java | 4 +- .../query/filter/HierarchyDirectRelation.java | 4 +- .../api/query/filter/HierarchyExcluding.java | 4 +- .../query/filter/HierarchyExcludingRoot.java | 2 + .../api/query/filter/HierarchyHaving.java | 4 +- .../api/query/filter/HierarchyWithin.java | 4 +- .../api/query/filter/HierarchyWithinRoot.java | 4 +- .../java/io/evitadb/api/query/filter/Not.java | 2 + .../java/io/evitadb/api/query/filter/Or.java | 4 +- .../api/query/filter/PriceBetween.java | 4 +- .../api/query/filter/PriceInCurrency.java | 4 +- .../api/query/filter/PriceInPriceLists.java | 4 +- .../api/query/filter/PriceValidIn.java | 4 +- .../api/query/filter/ReferenceHaving.java | 4 +- .../evitadb/api/query/filter/UserFilter.java | 4 +- .../io/evitadb/api/query/head/Collection.java | 4 +- .../api/query/order/AttributeNatural.java | 4 +- .../api/query/order/AttributeSetExact.java | 4 +- .../api/query/order/AttributeSetInFilter.java | 4 +- .../api/query/order/EntityGroupProperty.java | 4 +- .../query/order/EntityPrimaryKeyExact.java | 4 +- .../query/order/EntityPrimaryKeyInFilter.java | 4 +- .../query/order/EntityPrimaryKeyNatural.java | 4 +- .../api/query/order/EntityProperty.java | 4 +- .../io/evitadb/api/query/order/OrderBy.java | 4 +- .../evitadb/api/query/order/OrderGroupBy.java | 4 +- .../evitadb/api/query/order/PriceNatural.java | 4 +- .../io/evitadb/api/query/order/Random.java | 4 +- .../api/query/order/ReferenceProperty.java | 4 +- .../query/require/AssociatedDataContent.java | 4 +- .../api/query/require/AttributeContent.java | 4 +- .../api/query/require/AttributeHistogram.java | 4 +- .../api/query/require/DataInLocales.java | 4 +- .../api/query/require/EntityFetch.java | 4 +- .../api/query/require/EntityGroupFetch.java | 4 +- .../query/require/FacetGroupsConjunction.java | 4 +- .../query/require/FacetGroupsDisjunction.java | 4 +- .../query/require/FacetGroupsNegation.java | 4 +- .../api/query/require/FacetSummary.java | 4 +- .../require/FacetSummaryOfReference.java | 4 +- .../api/query/require/HierarchyChildren.java | 4 +- .../api/query/require/HierarchyContent.java | 4 +- .../api/query/require/HierarchyDistance.java | 4 +- .../api/query/require/HierarchyFromNode.java | 4 +- .../api/query/require/HierarchyFromRoot.java | 4 +- .../api/query/require/HierarchyLevel.java | 4 +- .../api/query/require/HierarchyNode.java | 4 +- .../query/require/HierarchyOfReference.java | 4 +- .../api/query/require/HierarchyOfSelf.java | 4 +- .../api/query/require/HierarchyParents.java | 4 +- .../api/query/require/HierarchySiblings.java | 4 +- .../query/require/HierarchyStatistics.java | 4 +- .../api/query/require/HierarchyStopAt.java | 4 +- .../io/evitadb/api/query/require/Page.java | 4 +- .../api/query/require/PriceContent.java | 4 +- .../api/query/require/PriceHistogram.java | 4 +- .../evitadb/api/query/require/PriceType.java | 4 +- .../api/query/require/QueryTelemetry.java | 4 +- .../api/query/require/ReferenceContent.java | 4 +- .../io/evitadb/api/query/require/Require.java | 4 +- .../io/evitadb/api/query/require/Strip.java | 4 +- 80 files changed, 297 insertions(+), 76 deletions(-) diff --git a/evita_functional_tests/src/test/java/io/evitadb/documentation/JavaDocCopy.java b/evita_functional_tests/src/test/java/io/evitadb/documentation/JavaDocCopy.java index 3a9b35ef2..9826703d4 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/documentation/JavaDocCopy.java +++ b/evita_functional_tests/src/test/java/io/evitadb/documentation/JavaDocCopy.java @@ -24,25 +24,31 @@ package io.evitadb.documentation; import com.thoughtworks.qdox.JavaProjectBuilder; +import com.thoughtworks.qdox.model.JavaAnnotation; import com.thoughtworks.qdox.model.JavaClass; import com.thoughtworks.qdox.model.JavaMethod; import com.thoughtworks.qdox.model.impl.DefaultJavaClass; import com.thoughtworks.qdox.model.impl.DefaultJavaMethod; import io.evitadb.api.query.Constraint; import io.evitadb.api.query.QueryConstraints; +import io.evitadb.api.query.descriptor.annotation.ConstraintDefinition; +import io.evitadb.exception.EvitaInternalError; import io.evitadb.test.EvitaTestSupport; import org.junit.jupiter.api.Test; import javax.annotation.Nonnull; import java.io.IOException; +import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Function; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -204,6 +210,63 @@ void copyJavaDocToQueryConstraints() throws IOException { ); } + @Test + void copyConstraintUserDocsLinksToJavaDocs() throws URISyntaxException, IOException { + final JavaProjectBuilder builder = new JavaProjectBuilder(); + + // add all source folders to the QDox library + for (String constraintRoot : CONSTRAINTS_ROOT) { + builder.addSourceTree(getRootDirectory().resolve(constraintRoot).toFile()); + } + + final Collection classesByName = builder.getClasses(); + + for (JavaClass constraintClass : classesByName) { + final Path constraintClassPath = Path.of(constraintClass.getParentSource().getURL().toURI()); + final List constraintSource = Files.readAllLines(constraintClassPath, StandardCharsets.UTF_8); + final Optional constraintDefinition = constraintClass.getAnnotations() + .stream() + .filter(it -> it.getType().getName().equals(ConstraintDefinition.class.getSimpleName())) + .findFirst(); + if (constraintDefinition.isEmpty()) { + // this class is not a constraint + continue; + } + final String userDocsLink = ((String) constraintDefinition.get().getNamedParameter("userDocsLink")).replace("\"", ""); + + int commentStartLine = -1; + for (int i = 0; i < constraintDefinition.get().getLineNumber(); i++) { + if (constraintSource.get(i).startsWith("/**")) { + commentStartLine = i; + break; + } + } + if (commentStartLine == -1) { + throw new EvitaInternalError("Could not find author line in `" + constraintClass.getName() + "`"); + } + final String comment = constraintClass.getComment(); + final int commentEndLine = comment.split("\n").length + commentStartLine; + + final int originalUserDocsLinkLineNumber = commentEndLine; + final String originalUserDocsLinkLine = constraintSource.get(originalUserDocsLinkLineNumber); + if (originalUserDocsLinkLine.contains("Visit detailed user documentation"); + } else { + // there is no link, add it + constraintSource.add(originalUserDocsLinkLineNumber + 1, " * Visit detailed user documentation"); + constraintSource.add(originalUserDocsLinkLineNumber + 1, " * "); + } + + // rewrite the file with replaced JavaDoc + Files.writeString( + constraintClassPath, + String.join("\n", constraintSource), + StandardOpenOption.TRUNCATE_EXISTING + ); + } + } + /** * Describes the locations of the factory methods in the class * diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/And.java b/evita_query/src/main/java/io/evitadb/api/query/filter/And.java index 5d6a9331c..9743574f9 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/And.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/And.java @@ -87,6 +87,8 @@ * * ... returns a single result - product with entity primary key 106742, which is the only one that all three * `entityPrimaryKeyInSet` constraints have in common. + * + * Visit detailed user documentation * * @author Jan Novotný, FG Forrest a.s. (c) 2021 */ @@ -116,4 +118,4 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) throw new UnsupportedOperationException("And filtering query has no arguments!"); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeBetween.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeBetween.java index 85cf1a51d..b347fd376 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeBetween.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeBetween.java @@ -80,6 +80,8 @@ * between("validity", 0, 1) * between("validity", 6, 7) * + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -132,4 +134,4 @@ public boolean isApplicable() { public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { return new AttributeBetween(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeContains.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeContains.java index f29743f31..dced31813 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeContains.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeContains.java @@ -55,6 +55,8 @@ * contains("code","mou") * contains("code","o") * + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -96,4 +98,4 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) return new AttributeContains(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeEndsWith.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeEndsWith.java index 6907e9d37..476ffa7d7 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeEndsWith.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeEndsWith.java @@ -56,6 +56,8 @@ * contains("code","at") * contains("code","og") * + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -97,4 +99,4 @@ public boolean isApplicable() { public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { return new AttributeEndsWith(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeEquals.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeEquals.java index e9860edb8..0e6470043 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeEquals.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeEquals.java @@ -57,6 +57,8 @@ * equals("code","B") * equals("code","C") * + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -99,4 +101,4 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) return new AttributeEquals(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeGreaterThan.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeGreaterThan.java index 86ca78c72..af895c3e3 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeGreaterThan.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeGreaterThan.java @@ -51,6 +51,8 @@ *
  * greaterThan("age", 20)
  * 
+ * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -93,4 +95,4 @@ public boolean isApplicable() { public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { return new AttributeGreaterThan(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeGreaterThanEquals.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeGreaterThanEquals.java index 3ccc908fc..f88faaa92 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeGreaterThanEquals.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeGreaterThanEquals.java @@ -52,6 +52,8 @@ *
  * greaterThanEquals("age", 20)
  * 
+ * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -94,4 +96,4 @@ public boolean isApplicable() { public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { return new AttributeGreaterThanEquals(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeInRange.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeInRange.java index 3858c45e4..f1450eac8 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeInRange.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeInRange.java @@ -72,6 +72,8 @@ * inRange("age", 24) * inRange("age", 63) * + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -173,4 +175,4 @@ public Optional getSuffixIfApplied() { public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { return new AttributeInRange(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeInSet.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeInSet.java index 2619a80a1..780664537 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeInSet.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeInSet.java @@ -59,6 +59,8 @@ * inSet("code","A","D") * inSet("code","A", "B") * + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -103,4 +105,4 @@ public boolean isApplicable() { public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { return new AttributeInSet(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeIs.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeIs.java index de8563437..adbfe5446 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeIs.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeIs.java @@ -51,6 +51,8 @@ * * * Function supports attribute arrays in the same way as plain values. + * + * Visit detailed user documentation * * @see AttributeSpecialValue * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 @@ -93,4 +95,4 @@ public boolean isApplicable() { public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { return new AttributeIs(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeLessThan.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeLessThan.java index a2765b0d9..d694daebb 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeLessThan.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeLessThan.java @@ -51,6 +51,8 @@ *
  * lessThan("age", 20)
  * 
+ * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -93,4 +95,4 @@ public boolean isApplicable() { public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { return new AttributeLessThan(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeLessThanEquals.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeLessThanEquals.java index 4295b9b3c..ea6e733d8 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeLessThanEquals.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeLessThanEquals.java @@ -52,6 +52,8 @@ *
  * lessThanEquals("age", 20)
  * 
+ * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -94,4 +96,4 @@ public boolean isApplicable() { public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { return new AttributeLessThanEquals(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeStartsWith.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeStartsWith.java index 41f0a1d42..889d56277 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeStartsWith.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeStartsWith.java @@ -56,6 +56,8 @@ * contains("code","mou") * contains("code","do") * + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -100,4 +102,4 @@ public boolean isApplicable() { public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { return new AttributeStartsWith(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/EntityHaving.java b/evita_query/src/main/java/io/evitadb/api/query/filter/EntityHaving.java index 947dd4876..75fc2b240 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/EntityHaving.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/EntityHaving.java @@ -51,6 +51,8 @@ * ) * ) * + * + * Visit detailed user documentation * * @author Lukáš Hornych, FG Forrest a.s. (c) 2022 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/EntityLocaleEquals.java b/evita_query/src/main/java/io/evitadb/api/query/filter/EntityLocaleEquals.java index f1445c570..b8d8b319d 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/EntityLocaleEquals.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/EntityLocaleEquals.java @@ -65,6 +65,8 @@ * ) * ) * + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -106,4 +108,4 @@ public boolean isApplicable() { public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { return new EntityLocaleEquals(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/EntityPrimaryKeyInSet.java b/evita_query/src/main/java/io/evitadb/api/query/filter/EntityPrimaryKeyInSet.java index d9c0ef3ec..ea641ec6d 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/EntityPrimaryKeyInSet.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/EntityPrimaryKeyInSet.java @@ -43,6 +43,8 @@ *
  * primaryKey(1, 2, 3)
  * 
+ * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -81,4 +83,4 @@ public int[] getPrimaryKeys() { public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { return new EntityPrimaryKeyInSet(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/FacetHaving.java b/evita_query/src/main/java/io/evitadb/api/query/filter/FacetHaving.java index e67948f76..c22918471 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/FacetHaving.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/FacetHaving.java @@ -56,6 +56,8 @@ * ) * ) * + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -118,4 +120,4 @@ public FilterConstraint getCopyWithNewChildren(@Nonnull FilterConstraint[] child public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { return new FacetHaving(newArguments, getChildren()); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/FilterBy.java b/evita_query/src/main/java/io/evitadb/api/query/filter/FilterBy.java index 2e7100fbe..c22b416d7 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/FilterBy.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/FilterBy.java @@ -52,6 +52,8 @@ * ) * ) * + * + * Visit detailed user documentation * * @author Jan Novotný, FG Forrest a.s. (c) 2021 */ @@ -90,4 +92,4 @@ public FilterConstraint getCopyWithNewChildren(@Nonnull FilterConstraint[] child return children.length > 0 ? new FilterBy(children) : new FilterBy(); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/FilterGroupBy.java b/evita_query/src/main/java/io/evitadb/api/query/filter/FilterGroupBy.java index 760beb18b..33ce3f060 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/FilterGroupBy.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/FilterGroupBy.java @@ -57,6 +57,8 @@ * ) * ) * + * + * Visit detailed user documentation * * @author Jan Novotný, FG Forrest a.s. (c) 2021 */ @@ -96,4 +98,4 @@ public FilterConstraint getCopyWithNewChildren(@Nonnull FilterConstraint[] child return children.length > 0 ? new FilterGroupBy(children) : new FilterGroupBy(); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyDirectRelation.java b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyDirectRelation.java index 655cdff16..1e6834fc6 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyDirectRelation.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyDirectRelation.java @@ -85,6 +85,8 @@ * ) * ) * + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -120,4 +122,4 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) return new HierarchyDirectRelation(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyExcluding.java b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyExcluding.java index 15dd84fba..8bb6f2611 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyExcluding.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyExcluding.java @@ -120,6 +120,8 @@ * The hierarchical query traverses from the root nodes to the leaf nodes. For each of the nodes, the engine checks * whether the excluding constraint is satisfied valid, and if so, it excludes that hierarchy node and all of its child * nodes (entire subtree). + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -172,4 +174,4 @@ public FilterConstraint getCopyWithNewChildren(@Nonnull FilterConstraint[] child ); return new HierarchyExcluding(children); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyExcludingRoot.java b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyExcludingRoot.java index f414da35a..2215684b9 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyExcludingRoot.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyExcludingRoot.java @@ -94,6 +94,8 @@ * * ... we get only 4 items, which means that 16 were assigned directly to Keyboards category and only 4 of them were * assigned to Exotic keyboards. + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyHaving.java b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyHaving.java index a86bdfa03..056c8a5ac 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyHaving.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyHaving.java @@ -131,6 +131,8 @@ * In the situation where the single product, let's say Garmin Vivosmart 5, is in both the excluded category Christmas * Electronics and the included category Smartwatches, it will remain in the query result because there is at least one * product reference that is part of the visible part of the tree. + * + * Visit detailed user documentation * * @author Lukáš Hornych, FG Forrest a.s. (c) 2023 */ @@ -183,4 +185,4 @@ public FilterConstraint getCopyWithNewChildren(@Nonnull FilterConstraint[] child ); return new HierarchyHaving(children); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyWithin.java b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyWithin.java index a25b66f50..4085ddd6a 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyWithin.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyWithin.java @@ -107,6 +107,8 @@ * * Products assigned to two or more subcategories of Accessories category will only appear once in the response * (contrary to what you might expect if you have experience with SQL). + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -265,4 +267,4 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) return new HierarchyWithin(newArguments, getChildren(), getExcludedChildrenFilter()); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyWithinRoot.java b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyWithinRoot.java index 3f19c4f19..793671cfa 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyWithinRoot.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyWithinRoot.java @@ -104,6 +104,8 @@ * * Products assigned to only one orphan category will be missing from the result. Products assigned to two or more * categories will only appear once in the response (contrary to what you might expect if you have experience with SQL). + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -239,4 +241,4 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) return new HierarchyWithinRoot(newArguments, getChildren(), getAdditionalChildren()); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/Not.java b/evita_query/src/main/java/io/evitadb/api/query/filter/Not.java index 77955cdce..e632b7d7b 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/Not.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/Not.java @@ -86,6 +86,8 @@ * * * ... which returns only three products that were not excluded by the following `not` constraint. + * + * Visit detailed user documentation * * @author Jan Novotný, FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/Or.java b/evita_query/src/main/java/io/evitadb/api/query/filter/Or.java index 6b6c1e852..8fa2081a2 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/Or.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/Or.java @@ -87,6 +87,8 @@ * * ... returns four results representing a combination of all primary keys used in the `entityPrimaryKeyInSet` * constraints. + * + * Visit detailed user documentation * * @author Jan Novotný, FG Forrest a.s. (c) 2021 */ @@ -116,4 +118,4 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) throw new UnsupportedOperationException("Or filtering query has no arguments!"); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/PriceBetween.java b/evita_query/src/main/java/io/evitadb/api/query/filter/PriceBetween.java index 4bcab08e2..3f13fc257 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/PriceBetween.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/PriceBetween.java @@ -50,6 +50,8 @@ * Warning: Only a single occurrence of any of this constraint is allowed in the filter part of the query. * Currently, there is no way to switch context between different parts of the filter and build queries such as find * a product whose price is either in "CZK" or "EUR" currency at this or that time using this constraint. + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -98,4 +100,4 @@ public boolean isApplicable() { public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { return new PriceBetween(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/PriceInCurrency.java b/evita_query/src/main/java/io/evitadb/api/query/filter/PriceInCurrency.java index b11e8ac56..4afbe22dd 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/PriceInCurrency.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/PriceInCurrency.java @@ -46,6 +46,8 @@ * Warning: Only a single occurrence of any of this constraint is allowed in the filter part of the query. * Currently, there is no way to switch context between different parts of the filter and build queries such as find * a product whose price is either in "CZK" or "EUR" currency at this or that time using this constraint. + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -90,4 +92,4 @@ public boolean isApplicable() { public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { return new PriceInCurrency(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/PriceInPriceLists.java b/evita_query/src/main/java/io/evitadb/api/query/filter/PriceInPriceLists.java index aa6d02776..99893ffce 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/PriceInPriceLists.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/PriceInPriceLists.java @@ -57,6 +57,8 @@ * Warning: Only a single occurrence of any of this constraint is allowed in the filter part of the query. * Currently, there is no way to switch context between different parts of the filter and build queries such as find * a product whose price is either in "CZK" or "EUR" currency at this or that time using this constraint. + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -93,4 +95,4 @@ public String[] getPriceLists() { public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { return new PriceInPriceLists(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/PriceValidIn.java b/evita_query/src/main/java/io/evitadb/api/query/filter/PriceValidIn.java index 28d896bca..d8062624c 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/PriceValidIn.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/PriceValidIn.java @@ -53,6 +53,8 @@ * Warning: Only a single occurrence of any of this constraint is allowed in the filter part of the query. * Currently, there is no way to switch context between different parts of the filter and build queries such as find * a product whose price is either in "CZK" or "EUR" currency at this or that time using this constraint. + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -105,4 +107,4 @@ public boolean isApplicable() { public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { return new PriceValidIn(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/ReferenceHaving.java b/evita_query/src/main/java/io/evitadb/api/query/filter/ReferenceHaving.java index df73e52e5..329cba635 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/ReferenceHaving.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/ReferenceHaving.java @@ -65,6 +65,8 @@ * entityPrimaryKeyInSet(1) * ) * + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -128,4 +130,4 @@ public FilterConstraint getCopyWithNewChildren(@Nonnull FilterConstraint[] child public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { return new ReferenceHaving(newArguments, getChildren()); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/UserFilter.java b/evita_query/src/main/java/io/evitadb/api/query/filter/UserFilter.java index c76580345..737669398 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/UserFilter.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/UserFilter.java @@ -59,6 +59,8 @@ * ) * ) * + * + * Visit detailed user documentation * * @author Jan Novotný, FG Forrest a.s. (c) 2021 */ @@ -135,4 +137,4 @@ public FilterConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) throw new UnsupportedOperationException("UserFilter filtering query has no arguments!"); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/head/Collection.java b/evita_query/src/main/java/io/evitadb/api/query/head/Collection.java index 0bf66f44b..ff65377c8 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/head/Collection.java +++ b/evita_query/src/main/java/io/evitadb/api/query/head/Collection.java @@ -44,6 +44,8 @@ *
  * collection('category')
  * 
+ * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -93,4 +95,4 @@ public void accept(@Nonnull ConstraintVisitor visitor) { public HeadConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { return new Collection(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/AttributeNatural.java b/evita_query/src/main/java/io/evitadb/api/query/order/AttributeNatural.java index 78aca411a..02b21e725 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/AttributeNatural.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/AttributeNatural.java @@ -75,6 +75,8 @@ * compound will cover multiple attributes and prepares a special sort index for this particular combination of * attributes, respecting the predefined order and NULL values behaviour. In the query, you can then use the compound * name instead of the default attribute name and achieve the expected results. + * + * Visit detailed user documentation * * @author Lukáš Hornych, FG Forrest a.s. (c) 2022 */ @@ -129,4 +131,4 @@ public OrderDirection getOrderDirection() { public OrderConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { return new AttributeNatural(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/AttributeSetExact.java b/evita_query/src/main/java/io/evitadb/api/query/order/AttributeSetExact.java index 56b16beff..3df96f0d7 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/AttributeSetExact.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/AttributeSetExact.java @@ -58,6 +58,8 @@ * stated in the second to Nth argument of this ordering constraint. If there are entities, that have not the attribute * `code` , then they will be present at the end of the output in ascending order of their primary keys (or they will be * sorted by additional ordering constraint in the chain). + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -115,4 +117,4 @@ public boolean isApplicable() { public OrderConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { return new AttributeSetExact(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/AttributeSetInFilter.java b/evita_query/src/main/java/io/evitadb/api/query/order/AttributeSetInFilter.java index 3f582a7c9..ebb075062 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/AttributeSetInFilter.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/AttributeSetInFilter.java @@ -59,6 +59,8 @@ * The example will return the selected entities (if present) in the exact order of their attribute `code` that was used * for array filtering them. The ordering constraint is particularly useful when you have sorted set of attribute values * from an external system which needs to be maintained (for example, it represents a relevancy of those entities). + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -98,4 +100,4 @@ public OrderConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) ); return new AttributeSetInFilter(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/EntityGroupProperty.java b/evita_query/src/main/java/io/evitadb/api/query/order/EntityGroupProperty.java index 75fba0553..860b463cc 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/EntityGroupProperty.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/EntityGroupProperty.java @@ -104,6 +104,8 @@ * ) * ) * + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -142,4 +144,4 @@ public boolean isNecessary() { public OrderConstraint getCopyWithNewChildren(@Nonnull OrderConstraint[] children, @Nonnull Constraint[] additionalChildren) { return new EntityGroupProperty(children); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyExact.java b/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyExact.java index 88d2cf805..d961cbdce 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyExact.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyExact.java @@ -55,6 +55,8 @@ * this ordering constraint. If there are entities, whose primary keys are not present in the argument, then they * will be present at the end of the output in ascending order of their primary keys (or they will be sorted by * additional ordering constraint in the chain). + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -91,4 +93,4 @@ public int[] getPrimaryKeys() { public OrderConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { return new EntityPrimaryKeyExact(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyInFilter.java b/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyInFilter.java index d298d18ab..bb9842222 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyInFilter.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyInFilter.java @@ -57,6 +57,8 @@ * The example will return the selected entities (if present) in the exact order that was used for array filtering them. * The ordering constraint is particularly useful when you have sorted set of entity primary keys from an external * system which needs to be maintained (for example, it represents a relevancy of those entities). + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -92,4 +94,4 @@ public OrderConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) ); return new EntityPrimaryKeyInFilter(); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyNatural.java b/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyNatural.java index 00dbb4c6f..8d3462e46 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyNatural.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyNatural.java @@ -49,6 +49,8 @@ * The example will return the selected entities (if present) in the descending order of their primary keys. Since * the entities are by default ordered by their primary key in ascending order, it has no sense to use this constraint * with {@link OrderDirection#ASC} direction. + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -83,4 +85,4 @@ public OrderDirection getOrderDirection() { public OrderConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { return new EntityPrimaryKeyNatural(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/EntityProperty.java b/evita_query/src/main/java/io/evitadb/api/query/order/EntityProperty.java index f017d1854..969c57236 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/EntityProperty.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/EntityProperty.java @@ -70,6 +70,8 @@ * ) * ) * + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -108,4 +110,4 @@ public boolean isNecessary() { public OrderConstraint getCopyWithNewChildren(@Nonnull OrderConstraint[] children, @Nonnull Constraint[] additionalChildren) { return new EntityProperty(children); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/OrderBy.java b/evita_query/src/main/java/io/evitadb/api/query/order/OrderBy.java index 2dfb13ae2..270d24b43 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/OrderBy.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/OrderBy.java @@ -62,6 +62,8 @@ * priceDescending() * ) * + * + * Visit detailed user documentation * * @author Jan Novotný, FG Forrest a.s. (c) 2021 */ @@ -99,4 +101,4 @@ public boolean isNecessary() { public OrderConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { throw new UnsupportedOperationException("OrderBy ordering query has no arguments!"); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/OrderGroupBy.java b/evita_query/src/main/java/io/evitadb/api/query/order/OrderGroupBy.java index a9c151535..1d0886bac 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/OrderGroupBy.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/OrderGroupBy.java @@ -79,6 +79,8 @@ * ) * ) * + * + * Visit detailed user documentation * * @author Jan Novotný, FG Forrest a.s. (c) 2021 */ @@ -117,4 +119,4 @@ public boolean isNecessary() { public OrderConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { throw new UnsupportedOperationException("OrderGroupBy ordering query has no arguments!"); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/PriceNatural.java b/evita_query/src/main/java/io/evitadb/api/query/order/PriceNatural.java index bd1185c28..3bf0cad39 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/PriceNatural.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/PriceNatural.java @@ -49,6 +49,8 @@ * priceNatural() * priceNatural(DESC) * + * + * Visit detailed user documentation * * @author Lukáš Hornych, FG Forrest a.s. (c) 2022 */ @@ -93,4 +95,4 @@ public OrderConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) return new PriceNatural(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/Random.java b/evita_query/src/main/java/io/evitadb/api/query/order/Random.java index 524760802..ef469e783 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/Random.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/Random.java @@ -43,6 +43,8 @@ *
  * random()
  * 
+ * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -74,4 +76,4 @@ public boolean isApplicable() { public OrderConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { return new Random(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/ReferenceProperty.java b/evita_query/src/main/java/io/evitadb/api/query/order/ReferenceProperty.java index acb618263..137260fba 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/ReferenceProperty.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/ReferenceProperty.java @@ -110,6 +110,8 @@ * order them by a property "priority" set on the reference to the category, the first products will be those directly * related to the category, ordered by "priority", followed by the products of the first child category, and so on, * maintaining the depth-first order of the category tree. + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -156,4 +158,4 @@ public boolean isNecessary() { public OrderConstraint getCopyWithNewChildren(@Nonnull OrderConstraint[] children, @Nonnull Constraint[] additionalChildren) { return new ReferenceProperty(getReferenceName(), children); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/AssociatedDataContent.java b/evita_query/src/main/java/io/evitadb/api/query/require/AssociatedDataContent.java index 5e10d6eab..852a09f12 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/AssociatedDataContent.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/AssociatedDataContent.java @@ -58,6 +58,8 @@ *
  * associatedData("description", "gallery-3d")
  * 
+ * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -136,4 +138,4 @@ public T combineWith(@Nonnull T anotherRequirem public RequireConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { return new AssociatedDataContent(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/AttributeContent.java b/evita_query/src/main/java/io/evitadb/api/query/require/AttributeContent.java index 0ebd9f8fb..f1918a062 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/AttributeContent.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/AttributeContent.java @@ -62,6 +62,8 @@ * attributeContent("code", "name") * ) * + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -151,4 +153,4 @@ public T combineWith(@Nonnull T anotherRequirem public RequireConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { return new AttributeContent(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/AttributeHistogram.java b/evita_query/src/main/java/io/evitadb/api/query/require/AttributeHistogram.java index a3e9c2337..9b9dcc312 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/AttributeHistogram.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/AttributeHistogram.java @@ -50,6 +50,8 @@ *
  * attributeHistogram(5, "width", "height")
  * 
+ * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -103,4 +105,4 @@ public boolean isApplicable() { public RequireConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { return new AttributeHistogram(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/DataInLocales.java b/evita_query/src/main/java/io/evitadb/api/query/require/DataInLocales.java index 2ca6551ad..2e62f0422 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/DataInLocales.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/DataInLocales.java @@ -65,6 +65,8 @@ *
  * dataInLocalesAll()
  * 
+ * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -141,4 +143,4 @@ public RequireConstraint cloneWithArguments(@Nonnull Serializable[] newArguments public Optional getSuffixIfApplied() { return isAllRequested() ? Optional.of(SUFFIX_ALL) : Optional.empty(); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/EntityFetch.java b/evita_query/src/main/java/io/evitadb/api/query/require/EntityFetch.java index 3d4fd4e18..3ad3dc396 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/EntityFetch.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/EntityFetch.java @@ -67,6 +67,8 @@ * - {@link PriceContent} * - {@link HierarchyContent} * - {@link ReferenceContent} + * + * Visit detailed user documentation * * @author Lukáš Hornych, FG Forrest a.s. (c) 2022 */ @@ -137,4 +139,4 @@ public RequireConstraint getCopyWithNewChildren(@Nonnull RequireConstraint[] chi return new EntityFetch(children); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/EntityGroupFetch.java b/evita_query/src/main/java/io/evitadb/api/query/require/EntityGroupFetch.java index 455779603..79a58db6e 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/EntityGroupFetch.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/EntityGroupFetch.java @@ -71,6 +71,8 @@ * - {@link PriceContent} * - {@link HierarchyContent} * - {@link ReferenceContent} + * + * Visit detailed user documentation * * @author Lukáš Hornych, FG Forrest a.s. (c) 2022 */ @@ -143,4 +145,4 @@ public RequireConstraint cloneWithArguments(@Nonnull Serializable[] newArguments public RequireConstraint getCopyWithNewChildren(@Nonnull RequireConstraint[] children, @Nonnull Constraint[] additionalChildren) { return new EntityGroupFetch(children); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsConjunction.java b/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsConjunction.java index ee8b244d6..c98a30c48 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsConjunction.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsConjunction.java @@ -94,6 +94,8 @@ * When user selects facets: blue (11), red (12) by default relation would be: get all entities that have facet blue(11) OR * facet red(12). If require `facetGroupsConjunction('parameterType', 1)` is passed in the query filtering condition will * be composed as: blue(11) AND red(12) + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -155,4 +157,4 @@ public RequireConstraint getCopyWithNewChildren(@Nonnull RequireConstraint[] chi Assert.isPremiseValid(ArrayUtils.isEmpty(children), "Children must be empty"); return new FacetGroupsConjunction(getArguments(), additionalChildren); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsDisjunction.java b/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsDisjunction.java index daf6cdff4..27fed7001 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsDisjunction.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsDisjunction.java @@ -94,6 +94,8 @@ * When user selects facets: blue (11), large (22), new products (31) - the default meaning would be: get all entities that * have facet blue as well as facet large and action products tag (AND). If require `facetGroupsDisjunction('tag', 3)` * is passed in the query, filtering condition will be composed as: (`blue(11)` AND `large(22)`) OR `new products(31)` + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -155,4 +157,4 @@ public RequireConstraint getCopyWithNewChildren(@Nonnull RequireConstraint[] chi Assert.isPremiseValid(ArrayUtils.isEmpty(children), "Children must be empty"); return new FacetGroupsDisjunction(getArguments(), additionalChildren); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsNegation.java b/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsNegation.java index c0b8d7cb4..1179783ef 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsNegation.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsNegation.java @@ -75,6 +75,8 @@ * The predicted results in the negated groups are far greater than the numbers produced by the default behavior. * Selecting any option in the RAM facet group predicts returning thousands of results, while the ROM facet group with * default behavior predicts only a dozen of them. + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -139,4 +141,4 @@ public RequireConstraint getCopyWithNewChildren(@Nonnull RequireConstraint[] chi Assert.isPremiseValid(ArrayUtils.isEmpty(children), "Children must be empty"); return new FacetGroupsNegation(getArguments(), additionalChildren); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/FacetSummary.java b/evita_query/src/main/java/io/evitadb/api/query/require/FacetSummary.java index 29e79c31c..86f089ae7 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/FacetSummary.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/FacetSummary.java @@ -122,6 +122,8 @@ * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -313,4 +315,4 @@ public RequireConstraint cloneWithArguments(@Nonnull Serializable[] newArguments getAdditionalChildren() ); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/FacetSummaryOfReference.java b/evita_query/src/main/java/io/evitadb/api/query/require/FacetSummaryOfReference.java index 9e640d37e..4152278d7 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/FacetSummaryOfReference.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/FacetSummaryOfReference.java @@ -117,6 +117,8 @@ * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -296,4 +298,4 @@ public RequireConstraint cloneWithArguments(@Nonnull Serializable[] newArguments getAdditionalChildren() ); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyChildren.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyChildren.java index d3fd2fecd..9dc6dc067 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyChildren.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyChildren.java @@ -103,6 +103,8 @@ * well. The reason is simple: when you render a menu for the query result, you want the calculated statistics to * respect the rules that apply to the hierarchyWithin so that the calculated number remains consistent for the end * user. + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2023 */ @@ -231,4 +233,4 @@ public RequireConstraint cloneWithArguments(@Nonnull Serializable[] newArguments return new HierarchyChildren((String) newArguments[0], getChildren(), getAdditionalChildren()); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyContent.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyContent.java index bad3da43b..89df421ae 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyContent.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyContent.java @@ -65,6 +65,8 @@ * hierarchyContent() * ) * + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -165,4 +167,4 @@ public RequireConstraint cloneWithArguments(@Nonnull Serializable[] newArguments Assert.isTrue(ArrayUtils.isEmpty(newArguments), "Additional children are not supported for HierarchyContent!"); return this; } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyDistance.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyDistance.java index fa223a046..31005f15c 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyDistance.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyDistance.java @@ -72,6 +72,8 @@ * The following query lists products in category Audio and its subcategories. Along with the products returned, it * also returns a computed subcategories data structure that lists the flat category list the currently focused category * Audio. + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2023 */ @@ -121,4 +123,4 @@ public RequireConstraint cloneWithArguments(@Nonnull Serializable[] newArguments return new HierarchyDistance(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyFromNode.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyFromNode.java index 02bae86aa..ad436edc8 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyFromNode.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyFromNode.java @@ -124,6 +124,8 @@ * the `fromNode` respects them. The reason is simple: when you render a menu for the query result, you want * the calculated statistics to respect the rules that apply to the hierarchyWithin so that the calculated number * remains consistent for the end user. + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2023 */ @@ -276,4 +278,4 @@ public RequireConstraint cloneWithArguments(@Nonnull Serializable[] newArguments return new HierarchyFromNode((String) newArguments[0], getChildren(), getAdditionalChildren()); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyFromRoot.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyFromRoot.java index 19a2cfde6..65b2b781b 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyFromRoot.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyFromRoot.java @@ -106,6 +106,8 @@ * the `fromRoot` respects them. The reason is simple: when you render a menu for the query result, you want * the calculated statistics to respect the rules that apply to the {@link HierarchyWithin} so that the calculated * number remains consistent for the end user. + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2023 */ @@ -238,4 +240,4 @@ public RequireConstraint cloneWithArguments(@Nonnull Serializable[] newArguments return new HierarchyFromRoot((String) newArguments[0], getChildren(), getAdditionalChildren()); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyLevel.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyLevel.java index aa2f611c3..94c45d778 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyLevel.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyLevel.java @@ -65,6 +65,8 @@ * * The query lists products in Audio category and its subcategories. Along with the products returned, it * also returns a computed megaMenu data structure that lists top two levels of the entire hierarchy. + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2023 */ @@ -113,4 +115,4 @@ public RequireConstraint cloneWithArguments(@Nonnull Serializable[] newArguments return new HierarchyLevel(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyNode.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyNode.java index 0de8b2bb5..a9a3220c5 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyNode.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyNode.java @@ -77,6 +77,8 @@ * ) * ) * + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2023 */ @@ -132,4 +134,4 @@ public RequireConstraint cloneWithArguments(@Nonnull Serializable[] newArguments return this; } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyOfReference.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyOfReference.java index 55d5e4eb2..ecb7c8294 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyOfReference.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyOfReference.java @@ -79,6 +79,8 @@ * - {@link HierarchySiblings} * - {@link HierarchyChildren} * - {@link HierarchyParents} + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -247,4 +249,4 @@ public RequireConstraint cloneWithArguments(@Nonnull Serializable[] newArguments ); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyOfSelf.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyOfSelf.java index 0a3729c48..c48a468d0 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyOfSelf.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyOfSelf.java @@ -70,6 +70,8 @@ * - {@link HierarchySiblings} * - {@link HierarchyChildren} * - {@link HierarchyParents} + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -154,4 +156,4 @@ public RequireConstraint cloneWithArguments(@Nonnull Serializable[] newArguments throw new UnsupportedOperationException("This type of constraint doesn't support arguments!"); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyParents.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyParents.java index 4b7ea73f3..6bd5efc7f 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyParents.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyParents.java @@ -100,6 +100,8 @@ * the parents will respect them as well during child nodes / queried entities statistics calculation. The reason is * simple: when you render a menu for the query result, you want the calculated statistics to respect the rules that * apply to the {@link HierarchyWithin} so that the calculated number remains consistent for the end user. + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2023 */ @@ -263,4 +265,4 @@ public RequireConstraint cloneWithArguments(@Nonnull Serializable[] newArguments return new HierarchyParents((String) newArguments[0], getChildren()); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchySiblings.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchySiblings.java index 4bdfcb144..cde44c8fd 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchySiblings.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchySiblings.java @@ -110,6 +110,8 @@ * the first string argument that defines the name for the output data structure. The reason is that this name is * already defined on the enclosing parents constraint, and the siblings constraint simply extends the data available * in its data structure. + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2023 */ @@ -240,4 +242,4 @@ public RequireConstraint cloneWithArguments(@Nonnull Serializable[] newArguments return new HierarchySiblings((String) newArguments[0], getChildren(), getAdditionalChildren()); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyStatistics.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyStatistics.java index 160726022..66dadbfa1 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyStatistics.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyStatistics.java @@ -78,6 +78,8 @@ * This query actually has to filter and aggregate all the records in the database, which is obviously quite expensive, * even considering that all the indexes are in-memory. Caching is probably the only way out if you really need * to crunch these numbers. + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2023 */ @@ -178,4 +180,4 @@ public RequireConstraint cloneWithArguments(@Nonnull Serializable[] newArguments return new HierarchyStatistics(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyStopAt.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyStopAt.java index 3e548d070..a89989747 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyStopAt.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyStopAt.java @@ -49,6 +49,8 @@ * * which define the constraint that stops traversing the hierarchy tree when it's satisfied by a currently traversed * node. + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2023 */ @@ -144,4 +146,4 @@ public RequireConstraint cloneWithArguments(@Nonnull Serializable[] newArguments return this; } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/Page.java b/evita_query/src/main/java/io/evitadb/api/query/require/Page.java index d4c5db199..269471870 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/Page.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/Page.java @@ -53,6 +53,8 @@ *
  * page(1, 24)
  * 
+ * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -112,4 +114,4 @@ public RequireConstraint cloneWithArguments(@Nonnull Serializable[] newArguments return new Page(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/PriceContent.java b/evita_query/src/main/java/io/evitadb/api/query/require/PriceContent.java index f93fe7d39..7524cc2d2 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/PriceContent.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/PriceContent.java @@ -64,6 +64,8 @@ * priceContentRespectingFilter("reference") * priceContentAll() * + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -175,4 +177,4 @@ public RequireConstraint cloneWithArguments(@Nonnull Serializable[] newArguments return new PriceContent(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/PriceHistogram.java b/evita_query/src/main/java/io/evitadb/api/query/require/PriceHistogram.java index bd287353d..777209c1d 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/PriceHistogram.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/PriceHistogram.java @@ -48,6 +48,8 @@ *
  * priceHistogram(20)
  * 
+ * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -83,4 +85,4 @@ public int getRequestedBucketCount() { public RequireConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { return new PriceHistogram(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/PriceType.java b/evita_query/src/main/java/io/evitadb/api/query/require/PriceType.java index ca7f08a2b..59a2114cb 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/PriceType.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/PriceType.java @@ -49,6 +49,8 @@ * ``` * useOfPrice(WITH_TAX) * ``` + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -83,4 +85,4 @@ public RequireConstraint cloneWithArguments(@Nonnull Serializable[] newArguments return new PriceType(getQueryPriceMode()); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/QueryTelemetry.java b/evita_query/src/main/java/io/evitadb/api/query/require/QueryTelemetry.java index 8d6534ab3..7917dff69 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/QueryTelemetry.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/QueryTelemetry.java @@ -41,6 +41,8 @@ *
  * queryTelemetry()
  * 
+ * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -66,4 +68,4 @@ public QueryTelemetry() { public RequireConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { return new QueryTelemetry(newArguments); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/ReferenceContent.java b/evita_query/src/main/java/io/evitadb/api/query/require/ReferenceContent.java index 553d098d2..d03f1d9c3 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/ReferenceContent.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/ReferenceContent.java @@ -148,6 +148,8 @@ * ) * ) * + * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -435,4 +437,4 @@ public RequireConstraint cloneWithArguments(@Nonnull Serializable[] newArguments getAdditionalChildren() ); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/Require.java b/evita_query/src/main/java/io/evitadb/api/query/require/Require.java index ae518fdea..c23488103 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/Require.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/Require.java @@ -49,6 +49,8 @@ * entityFetch() * ) * + * + * Visit detailed user documentation * * @author Jan Novotný, FG Forrest a.s. (c) 2021 */ @@ -81,4 +83,4 @@ public boolean isNecessary() { public RequireConstraint cloneWithArguments(@Nonnull Serializable[] newArguments) { throw new UnsupportedOperationException("Require require query has no arguments!"); } -} +} \ No newline at end of file diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/Strip.java b/evita_query/src/main/java/io/evitadb/api/query/require/Strip.java index ba5d532f4..b64ed953d 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/Strip.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/Strip.java @@ -51,6 +51,8 @@ *
  * strip(52, 24)
  * 
+ * + * Visit detailed user documentation * * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ @@ -108,4 +110,4 @@ public RequireConstraint cloneWithArguments(@Nonnull Serializable[] newArguments return new Strip(newArguments); } -} +} \ No newline at end of file From 28f940f6a53783ddd7b2bbca297134adcfce6841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Novotn=C3=BD?= Date: Fri, 8 Dec 2023 15:10:16 +0100 Subject: [PATCH 12/52] docs: updated documentation --- .../api/example/associated-data-class.java | 1 + .../example/associated-data-interface.java | 1 + .../api/example/associated-data-record.java | 1 + .../en/use/api/example/attribute-class.java | 1 + .../use/api/example/attribute-interface.java | 1 + .../en/use/api/example/attribute-record.java | 1 + .../user/en/use/api/example/parent-class.java | 1 + .../en/use/api/example/parent-interface.java | 1 + .../en/use/api/example/parent-record.java | 1 + .../user/en/use/api/example/price-class.java | 1 + .../en/use/api/example/price-interface.java | 1 + .../user/en/use/api/example/price-record.java | 1 + .../en/use/api/example/primary-key-class.java | 1 + .../api/example/primary-key-interface.java | 1 + .../use/api/example/primary-key-record.java | 1 + .../en/use/api/example/reference-class.java | 125 +++++++++++++++++ .../use/api/example/reference-interface.java | 130 ++++++++++++++++++ .../en/use/api/example/reference-record.java | 129 +++++++++++++++++ documentation/user/en/use/api/query-data.md | 35 ++++- 19 files changed, 431 insertions(+), 3 deletions(-) create mode 100644 documentation/user/en/use/api/example/reference-class.java create mode 100644 documentation/user/en/use/api/example/reference-interface.java create mode 100644 documentation/user/en/use/api/example/reference-record.java diff --git a/documentation/user/en/use/api/example/associated-data-class.java b/documentation/user/en/use/api/example/associated-data-class.java index f19b08764..edf64d775 100644 --- a/documentation/user/en/use/api/example/associated-data-class.java +++ b/documentation/user/en/use/api/example/associated-data-class.java @@ -1,3 +1,4 @@ +@EntityRef("Product") @Data public class MyEntity { diff --git a/documentation/user/en/use/api/example/associated-data-interface.java b/documentation/user/en/use/api/example/associated-data-interface.java index 5ca6b7093..4df9c95f0 100644 --- a/documentation/user/en/use/api/example/associated-data-interface.java +++ b/documentation/user/en/use/api/example/associated-data-interface.java @@ -1,3 +1,4 @@ +@EntityRef("Product") public interface MyEntity { // returns associated data `warrantySpecification` if fetched and not null diff --git a/documentation/user/en/use/api/example/associated-data-record.java b/documentation/user/en/use/api/example/associated-data-record.java index a75c25c59..d195818a8 100644 --- a/documentation/user/en/use/api/example/associated-data-record.java +++ b/documentation/user/en/use/api/example/associated-data-record.java @@ -1,3 +1,4 @@ +@EntityRef("Product") public record MyEntity( // contains associated data `warrantySpecification` if fetched and not null @AssociatedData(warrantySpecification = "warrantySpecification", locale = true) diff --git a/documentation/user/en/use/api/example/attribute-class.java b/documentation/user/en/use/api/example/attribute-class.java index a6c1ea85d..a24cb916d 100644 --- a/documentation/user/en/use/api/example/attribute-class.java +++ b/documentation/user/en/use/api/example/attribute-class.java @@ -1,3 +1,4 @@ +@EntityRef("Product") @Data public class MyEntity { diff --git a/documentation/user/en/use/api/example/attribute-interface.java b/documentation/user/en/use/api/example/attribute-interface.java index d478bddbb..9b16e8961 100644 --- a/documentation/user/en/use/api/example/attribute-interface.java +++ b/documentation/user/en/use/api/example/attribute-interface.java @@ -1,3 +1,4 @@ +@EntityRef("Product") public interface MyEntity { // returns attribute `name` if fetched and not null diff --git a/documentation/user/en/use/api/example/attribute-record.java b/documentation/user/en/use/api/example/attribute-record.java index e96b08363..159f1991f 100644 --- a/documentation/user/en/use/api/example/attribute-record.java +++ b/documentation/user/en/use/api/example/attribute-record.java @@ -1,3 +1,4 @@ +@EntityRef("Product") public record MyEntity( // contains attribute `name` if fetched and not null @Attribute(name = "name", locale = true) diff --git a/documentation/user/en/use/api/example/parent-class.java b/documentation/user/en/use/api/example/parent-class.java index 0bc28e47a..1f982ec90 100644 --- a/documentation/user/en/use/api/example/parent-class.java +++ b/documentation/user/en/use/api/example/parent-class.java @@ -1,3 +1,4 @@ +@EntityRef("Product") @Data public class MyEntity { diff --git a/documentation/user/en/use/api/example/parent-interface.java b/documentation/user/en/use/api/example/parent-interface.java index 95266b5ff..e132281a0 100644 --- a/documentation/user/en/use/api/example/parent-interface.java +++ b/documentation/user/en/use/api/example/parent-interface.java @@ -1,3 +1,4 @@ +@EntityRef("Product") public interface MyEntity { // return id of parent entity, or null if this entity is a root entity diff --git a/documentation/user/en/use/api/example/parent-record.java b/documentation/user/en/use/api/example/parent-record.java index fd44df09d..c323b0240 100644 --- a/documentation/user/en/use/api/example/parent-record.java +++ b/documentation/user/en/use/api/example/parent-record.java @@ -1,3 +1,4 @@ +@EntityRef("Product") public record MyEntity( // contains id of parent entity, or null if this entity is a root entity @ParentEntity diff --git a/documentation/user/en/use/api/example/price-class.java b/documentation/user/en/use/api/example/price-class.java index 57cb5a0d9..dfb88a271 100644 --- a/documentation/user/en/use/api/example/price-class.java +++ b/documentation/user/en/use/api/example/price-class.java @@ -1,3 +1,4 @@ +@EntityRef("Product") @Data public record MyEntity( // contains the prices calculated as selling price for particular query diff --git a/documentation/user/en/use/api/example/price-interface.java b/documentation/user/en/use/api/example/price-interface.java index 88e295a47..e0f6c32b0 100644 --- a/documentation/user/en/use/api/example/price-interface.java +++ b/documentation/user/en/use/api/example/price-interface.java @@ -1,3 +1,4 @@ +@EntityRef("Product") public interface MyEntity { // returns the prices calculated as selling price for particular query diff --git a/documentation/user/en/use/api/example/price-record.java b/documentation/user/en/use/api/example/price-record.java index d383d064f..6a704b108 100644 --- a/documentation/user/en/use/api/example/price-record.java +++ b/documentation/user/en/use/api/example/price-record.java @@ -1,3 +1,4 @@ +@EntityRef("Product") public record MyEntity( // contains the prices calculated as selling price for particular query @PriceForSale diff --git a/documentation/user/en/use/api/example/primary-key-class.java b/documentation/user/en/use/api/example/primary-key-class.java index 2a4da15b9..b2281c17d 100644 --- a/documentation/user/en/use/api/example/primary-key-class.java +++ b/documentation/user/en/use/api/example/primary-key-class.java @@ -1,3 +1,4 @@ +@EntityRef("Product") @Data public class MyEntity { @PrimaryKey private final int id; diff --git a/documentation/user/en/use/api/example/primary-key-interface.java b/documentation/user/en/use/api/example/primary-key-interface.java index c2961a6fc..f7d5e3378 100644 --- a/documentation/user/en/use/api/example/primary-key-interface.java +++ b/documentation/user/en/use/api/example/primary-key-interface.java @@ -1,3 +1,4 @@ +@EntityRef("Product") public interface MyEntity { @PrimaryKey int any(); diff --git a/documentation/user/en/use/api/example/primary-key-record.java b/documentation/user/en/use/api/example/primary-key-record.java index 49547748e..0ef18e01f 100644 --- a/documentation/user/en/use/api/example/primary-key-record.java +++ b/documentation/user/en/use/api/example/primary-key-record.java @@ -1,3 +1,4 @@ +@EntityRef("Product") public record MyEntity( @PrimaryKey int id, @PrimaryKey Integer idAsIntegerObject, diff --git a/documentation/user/en/use/api/example/reference-class.java b/documentation/user/en/use/api/example/reference-class.java new file mode 100644 index 000000000..8a78bb22f --- /dev/null +++ b/documentation/user/en/use/api/example/reference-class.java @@ -0,0 +1,125 @@ +@EntityRef("Product") +@Data +public class MyEntity { + + // component contains referenced entity Brand if such reference with cardinality ZERO_OR_ONE exists + // and the Brand entity is fetched along with MyEntity + // throws ContextMissingException if the reference information was not fetched from the server + @ReferenceRef("brand") + @Nullable private final Brand brand, + + // component contains referenced Brand entity primary key if such reference with cardinality ZERO_OR_ONE exists + // throws ContextMissingException if the reference information was not fetched from the server + @ReferenceRef("brand") + @Nullable private final Integer brandId, + + // component contains collection of referenced ProductParameter references or empty list + // reference `parameters` has cardinality ZERO_OR_MORE + // throws ContextMissingException if the reference information was not fetched from the server + @ReferenceRef("parameters") + @NonNull private final List parameters, + + // alternative format for `Parameters` method with similar behaviour + @ReferenceRef("parameters") + @NonNull private final Set parametersAsSet, + + // alternative format for `Parameters` method with similar behaviour + @ReferenceRef("parameters") + @NonNull private final Collection parameterAsCollection, + + // alternative format for `Parameters` method with similar behaviour + @ReferenceRef("parameters") + @NonNull private final ProductParameter[] parameterAsArray, + + // component contains array of referenced Parameter entities or null + // reference `parameters` has cardinality ZERO_OR_MORE + // throws ContextMissingException if the reference information was not fetched from the server + @ReferenceRef("parameters") + @Nullable private final int[] parameterIds + + + // simplified Brand entity interface + // this example demonstrates the option to return directly referenced entities from the main entity + @EntityRef("Brand") + @Data + public class Brand extends Serializable { + @PrimaryKeyRef + private final int id, + + // attribute code of the Brand entity + @AttributeRef("code") + @Nullable private final String code + + } + + // simplified ProductParameter entity interface + // this example demonstrates the option to return interfaces covering the references that provide further access + // to the referenced entities (both grouping and referenced entities) + @EntityRef("Parameter") + @Data + public class ProductParameter extends Serializable { + + @ReferencedEntity + private final int primaryKey, + + // attribute code of the reference to the Parameter entity + @AttributeRef("priority") + @Nullable private final Long priority, + + // primary key of the referenced entity of the reference + @ReferencedEntity + @Nullable private final Integer parameter, + + // reference to the referenced entity descriptor of the reference + @ReferencedEntity + @Nullable private final EntityReferenceContract parameterEntityClassifier, + + // reference to the referenced entity of the reference + // throws ContextMissingException if the referenced entity was not fetched from the server + @ReferencedEntity + @Nullable private final Parameter parameterEntity, + + // primary key of the grouping entity of the reference to the Parameter entity + @ReferencedEntityGroup + @Nullable private final Integer parameterGroup, + + // reference to the grouping entity descriptor of the reference to the Parameter entity + @ReferencedEntityGroup + @Nullable private final EntityReferenceContract parameterGroupEntityClassifier, + + // reference to the grouping entity of the reference to the Parameter entity + // throws ContextMissingException if the referenced entity was not fetched from the server + @ReferencedEntityGroup + @Nullable private final ParameterGroup parameterGroupEntity + + } + + // simplified Parameter entity interface + @EntityRef("Parameter") + @Data + public class ParameterGroupInterfaceEditor extends Serializable { + + @PrimaryKeyRef + int id, + + // attribute code of the Parameter entity + @AttributeRef("code") + @Nullable private final String code + + } + + // simplified ParameterGroup entity interface + @EntityRef("ParameterGroup") + @Data + public class ParameterGroupInterfaceEditor extends Serializable { + + @PrimaryKeyRef + int id, + + // attribute code of the ParameterGroup entity + @AttributeRef("code") + @Nullable private final String code + + } + +} \ No newline at end of file diff --git a/documentation/user/en/use/api/example/reference-interface.java b/documentation/user/en/use/api/example/reference-interface.java new file mode 100644 index 000000000..2f0206dac --- /dev/null +++ b/documentation/user/en/use/api/example/reference-interface.java @@ -0,0 +1,130 @@ +@EntityRef("Product") +public interface MyEntity { + + // method returns referenced entity Brand if such reference with cardinality ZERO_OR_ONE exists + // and the Brand entity is fetched along with MyEntity + // throws ContextMissingException if the reference information was not fetched from the server + @ReferenceRef("brand") + @Nullable Brand getBrand() throws ContextMissingException; + + // method returns referenced Brand entity primary key if such reference with cardinality ZERO_OR_ONE exists + // throws ContextMissingException if the reference information was not fetched from the server + @ReferenceRef("brand") + @Nullable Integer getBrandId() throws ContextMissingException; + + // method returns collection of referenced ProductParameter references or empty list + // reference `parameters` has cardinality ZERO_OR_MORE + // throws ContextMissingException if the reference information was not fetched from the server + @ReferenceRef("parameters") + @NonNull List getParameters() throws ContextMissingException; + + // alternative format for `getParameters` method with similar behaviour + @ReferenceRef("parameters") + @NonNull Set getParametersAsSet() throws ContextMissingException; + + // alternative format for `getParameters` method with similar behaviour + @ReferenceRef("parameters") + @NonNull Collection getParameterAsCollection() throws ContextMissingException; + + // alternative format for `getParameters` method with similar behaviour + @ReferenceRef("parameters") + @NonNull ProductParameter[] getParameterAsArray() throws ContextMissingException; + + // method returns array of referenced Parameter entities or empty value if the reference information + // was not fetched from the server or there is no reference to Parameter from the current product + @ReferenceRef("parameters") + @NonNull Optional getParametersIfPresent(); + + // method returns array of referenced Parameter entities or null + // reference `parameters` has cardinality ZERO_OR_MORE + // throws ContextMissingException if the reference information was not fetched from the server + @ReferenceRef("parameters") + @Nullable int[] getParameterIds() throws ContextMissingException; + + // method returns array of referenced Parameter entity primary keys or empty value if the reference information + // was not fetched from the server or there is no reference to Parameter from the current product + @ReferenceRef("parameters") + @NonNull Optional getParameterIdsIfPresent(); + + // simplified Brand entity interface + // this example demonstrates the option to return directly referenced entities from the main entity + @EntityRef("Brand") + public interface Brand extends Serializable { + + @PrimaryKeyRef + int getId(); + + // attribute code of the Brand entity + @AttributeRef("code") + @Nullable String getCode(); + + } + + // simplified ProductParameter entity interface + // this example demonstrates the option to return interfaces covering the references that provide further access + // to the referenced entities (both grouping and referenced entities) + @EntityRef("Parameter") + public interface ProductParameter extends Serializable { + + @ReferencedEntity + int getPrimaryKey(); + + // attribute code of the reference to the Parameter entity + @AttributeRef("priority") + @Nullable Long getPriority(); + + // primary key of the referenced entity of the reference + @ReferencedEntity + @Nullable Integer getParameter(); + + // reference to the referenced entity descriptor of the reference + @ReferencedEntity + @Nullable EntityReferenceContract getParameterEntityClassifier(); + + // reference to the referenced entity of the reference + // throws ContextMissingException if the referenced entity was not fetched from the server + @ReferencedEntity + @Nullable Parameter getParameterEntity() throws ContextMissingException; + + // primary key of the grouping entity of the reference to the Parameter entity + @ReferencedEntityGroup + @Nullable Integer getParameterGroup(); + + // reference to the grouping entity descriptor of the reference to the Parameter entity + @ReferencedEntityGroup + @Nullable EntityReferenceContract getParameterGroupEntityClassifier(); + + // reference to the grouping entity of the reference to the Parameter entity + // throws ContextMissingException if the referenced entity was not fetched from the server + @ReferencedEntityGroup + @Nullable ParameterGroup getParameterGroupEntity() throws ContextMissingException; + + } + + // simplified Parameter entity interface + @EntityRef("Parameter") + public interface ParameterGroupInterfaceEditor extends Serializable { + + @PrimaryKeyRef + int getId(); + + // attribute code of the Parameter entity + @AttributeRef("code") + @Nullable String getCode(); + + } + + // simplified ParameterGroup entity interface + @EntityRef("ParameterGroup") + public interface ParameterGroupInterfaceEditor extends Serializable { + + @PrimaryKeyRef + int getId(); + + // attribute code of the ParameterGroup entity + @AttributeRef("code") + @Nullable String getCode(); + + } + +} \ No newline at end of file diff --git a/documentation/user/en/use/api/example/reference-record.java b/documentation/user/en/use/api/example/reference-record.java new file mode 100644 index 000000000..944aca646 --- /dev/null +++ b/documentation/user/en/use/api/example/reference-record.java @@ -0,0 +1,129 @@ +@EntityRef("Product") +public interface MyEntity( + + // component contains referenced entity Brand if such reference with cardinality ZERO_OR_ONE exists + // and the Brand entity is fetched along with MyEntity + // throws ContextMissingException if the reference information was not fetched from the server + @ReferenceRef("brand") + @Nullable Brand brand, + + // component contains referenced Brand entity primary key if such reference with cardinality ZERO_OR_ONE exists + // throws ContextMissingException if the reference information was not fetched from the server + @ReferenceRef("brand") + @Nullable Integer brandId, + + // component contains collection of referenced ProductParameter references or empty list + // reference `parameters` has cardinality ZERO_OR_MORE + // throws ContextMissingException if the reference information was not fetched from the server + @ReferenceRef("parameters") + @NonNull List parameters, + + // alternative format for `Parameters` method with similar behaviour + @ReferenceRef("parameters") + @NonNull Set parametersAsSet, + + // alternative format for `Parameters` method with similar behaviour + @ReferenceRef("parameters") + @NonNull Collection parameterAsCollection, + + // alternative format for `Parameters` method with similar behaviour + @ReferenceRef("parameters") + @NonNull ProductParameter[] parameterAsArray, + + // component contains array of referenced Parameter entities or null + // reference `parameters` has cardinality ZERO_OR_MORE + // throws ContextMissingException if the reference information was not fetched from the server + @ReferenceRef("parameters") + @Nullable int[] parameterIds + +) { + + // simplified Brand entity interface + // this example demonstrates the option to return directly referenced entities from the main entity + @EntityRef("Brand") + public record Brand( + @PrimaryKeyRef + int id, + + // attribute code of the Brand entity + @AttributeRef("code") + @Nullable String code + + ) extends Serializable { + + } + + // simplified ProductParameter entity interface + // this example demonstrates the option to return interfaces covering the references that provide further access + // to the referenced entities (both grouping and referenced entities) + @EntityRef("Parameter") + public record ProductParameter( + + @ReferencedEntity + int primaryKey, + + // attribute code of the reference to the Parameter entity + @AttributeRef("priority") + @Nullable Long priority, + + // primary key of the referenced entity of the reference + @ReferencedEntity + @Nullable Integer parameter, + + // reference to the referenced entity descriptor of the reference + @ReferencedEntity + @Nullable EntityReferenceContract parameterEntityClassifier, + + // reference to the referenced entity of the reference + // throws ContextMissingException if the referenced entity was not fetched from the server + @ReferencedEntity + @Nullable Parameter parameterEntity, + + // primary key of the grouping entity of the reference to the Parameter entity + @ReferencedEntityGroup + @Nullable Integer parameterGroup, + + // reference to the grouping entity descriptor of the reference to the Parameter entity + @ReferencedEntityGroup + @Nullable EntityReferenceContract parameterGroupEntityClassifier, + + // reference to the grouping entity of the reference to the Parameter entity + // throws ContextMissingException if the referenced entity was not fetched from the server + @ReferencedEntityGroup + @Nullable ParameterGroup parameterGroupEntity + + ) extends Serializable { + + } + + // simplified Parameter entity interface + @EntityRef("Parameter") + public interface ParameterGroupInterfaceEditor( + + @PrimaryKeyRef + int id, + + // attribute code of the Parameter entity + @AttributeRef("code") + @Nullable String code + + ) extends Serializable { + + } + + // simplified ParameterGroup entity interface + @EntityRef("ParameterGroup") + public interface ParameterGroupInterfaceEditor( + + @PrimaryKeyRef + int id, + + // attribute code of the ParameterGroup entity + @AttributeRef("code") + @Nullable String code + + ) extends Serializable { + + } + +} \ No newline at end of file diff --git a/documentation/user/en/use/api/query-data.md b/documentation/user/en/use/api/query-data.md index bbf79c9f5..7e3a0d702 100644 --- a/documentation/user/en/use/api/query-data.md +++ b/documentation/user/en/use/api/query-data.md @@ -366,7 +366,7 @@ from ["complex data type"](../data-types.md#complex-data-types) using [documente #### Prices -To access the placement information of the entity prices, you must always work with +To access the entity prices, you must always work with evita_api/src/main/java/io/evitadb/api/requestResponse/data/PriceContract.java data type and annotate the methods with the evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/Price.java, evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/PriceForSale.java or @@ -390,7 +390,7 @@ because the method call may fail with a `NullPointerException` in such a case. #### Hierarchy -To access the placement information of the entity or hierarchy (i.e., its parent), you must use either the numeric data +To access the hierarchy placement information of the entity (i.e., its parent), you must use either the numeric data type, your own custom interface type, evita_api/src/main/java/io/evitadb/api/requestResponse/data/SealedEntity.java or evita_api/src/main/java/io/evitadb/api/requestResponse/data/structure/EntityReference.java data type and annotate it with the evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/ParentEntity.java @@ -410,7 +410,36 @@ because the method call may fail with a `NullPointerException` in such a case. #### References -TODO JNO - Work in progress +To access the references of the entity, you must use either the numeric data type, your own custom interface type, +evita_api/src/main/java/io/evitadb/api/requestResponse/data/EntityReferenceContract.java +or evita_api/src/main/java/io/evitadb/api/requestResponse/data/ReferenceContract.java +data type and annotate it with the evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/Reference.java +or evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/ReferenceRef.java +annotation. The datatype can be wrapped in +[Optional](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Optional.html) +(or its counterparts [OptionalInt](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/OptionalInt.html) +or [OptionalLong](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/OptionalLong.html)). + + +If the method can return multiple references, you need to wrap it in [Collection](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Collection.html) +(or its specializations [List](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/List.html) or [Set](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Set.html)). + + + +[Example interface with references](/documentation/user/en/use/api/example/reference-interface.java) + + + +When you declare to return a custom interface, you can return either the custom interface of the referenced entity +(i.e., an interface annotated with evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/Entity.java +or evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/EntityRef.java) +or interface mapping the reference. The latter allows you to access attributes of the reference and may also contain +additional methods to access the referenced entity. In the former case, you can access the referenced entity, but not +the reference attributes. + +Methods annotated with this annotation should respect the cardinality of the reference. If the cardinality is +`EXACTLY_ONE` or `ZERO_OR_ONE`, the method should directly return the entity or the reference to it. If the cardinality +is `ZERO_OR_MORE` or `ONE_OR_MORE`, the method should return a collection or array of entities or references to them. #### Access to evitaDB data structures From 5770ccd80a5500f0c841a167f07c6d71e0c0e6fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Fri, 8 Dec 2023 15:11:45 +0100 Subject: [PATCH 13/52] docs: constraint user docs links in QueryConstriants --- .../io/evitadb/documentation/JavaDocCopy.java | 7 +- .../evitadb/api/query/QueryConstraints.java | 438 ++++++++++++++++++ .../java/io/evitadb/api/query/filter/And.java | 2 +- .../api/query/filter/AttributeBetween.java | 2 +- .../api/query/filter/AttributeContains.java | 2 +- .../api/query/filter/AttributeEndsWith.java | 2 +- .../api/query/filter/AttributeEquals.java | 2 +- .../query/filter/AttributeGreaterThan.java | 2 +- .../filter/AttributeGreaterThanEquals.java | 2 +- .../api/query/filter/AttributeInRange.java | 2 +- .../api/query/filter/AttributeInSet.java | 2 +- .../evitadb/api/query/filter/AttributeIs.java | 2 +- .../api/query/filter/AttributeLessThan.java | 2 +- .../query/filter/AttributeLessThanEquals.java | 2 +- .../api/query/filter/AttributeStartsWith.java | 2 +- .../api/query/filter/EntityHaving.java | 2 +- .../api/query/filter/EntityLocaleEquals.java | 2 +- .../query/filter/EntityPrimaryKeyInSet.java | 2 +- .../evitadb/api/query/filter/FacetHaving.java | 2 +- .../io/evitadb/api/query/filter/FilterBy.java | 2 +- .../api/query/filter/FilterGroupBy.java | 2 +- .../query/filter/HierarchyDirectRelation.java | 2 +- .../api/query/filter/HierarchyExcluding.java | 2 +- .../query/filter/HierarchyExcludingRoot.java | 2 +- .../api/query/filter/HierarchyHaving.java | 2 +- .../api/query/filter/HierarchyWithin.java | 2 +- .../api/query/filter/HierarchyWithinRoot.java | 2 +- .../java/io/evitadb/api/query/filter/Not.java | 2 +- .../java/io/evitadb/api/query/filter/Or.java | 2 +- .../api/query/filter/PriceBetween.java | 2 +- .../api/query/filter/PriceInCurrency.java | 2 +- .../api/query/filter/PriceInPriceLists.java | 2 +- .../api/query/filter/PriceValidIn.java | 2 +- .../api/query/filter/ReferenceHaving.java | 2 +- .../evitadb/api/query/filter/UserFilter.java | 2 +- .../io/evitadb/api/query/head/Collection.java | 2 +- .../api/query/order/AttributeNatural.java | 2 +- .../api/query/order/AttributeSetExact.java | 2 +- .../api/query/order/AttributeSetInFilter.java | 2 +- .../api/query/order/EntityGroupProperty.java | 2 +- .../query/order/EntityPrimaryKeyExact.java | 2 +- .../query/order/EntityPrimaryKeyInFilter.java | 2 +- .../query/order/EntityPrimaryKeyNatural.java | 2 +- .../api/query/order/EntityProperty.java | 2 +- .../io/evitadb/api/query/order/OrderBy.java | 2 +- .../evitadb/api/query/order/OrderGroupBy.java | 2 +- .../evitadb/api/query/order/PriceNatural.java | 2 +- .../io/evitadb/api/query/order/Random.java | 2 +- .../api/query/order/ReferenceProperty.java | 2 +- .../query/require/AssociatedDataContent.java | 2 +- .../api/query/require/AttributeContent.java | 2 +- .../api/query/require/AttributeHistogram.java | 2 +- .../api/query/require/DataInLocales.java | 2 +- .../api/query/require/EntityFetch.java | 2 +- .../api/query/require/EntityGroupFetch.java | 2 +- .../query/require/FacetGroupsConjunction.java | 2 +- .../query/require/FacetGroupsDisjunction.java | 2 +- .../query/require/FacetGroupsNegation.java | 2 +- .../api/query/require/FacetSummary.java | 2 +- .../require/FacetSummaryOfReference.java | 2 +- .../api/query/require/HierarchyChildren.java | 2 +- .../api/query/require/HierarchyContent.java | 2 +- .../api/query/require/HierarchyDistance.java | 2 +- .../api/query/require/HierarchyFromNode.java | 2 +- .../api/query/require/HierarchyFromRoot.java | 2 +- .../api/query/require/HierarchyLevel.java | 2 +- .../api/query/require/HierarchyNode.java | 2 +- .../query/require/HierarchyOfReference.java | 2 +- .../api/query/require/HierarchyOfSelf.java | 2 +- .../api/query/require/HierarchyParents.java | 2 +- .../api/query/require/HierarchySiblings.java | 2 +- .../query/require/HierarchyStatistics.java | 2 +- .../api/query/require/HierarchyStopAt.java | 2 +- .../io/evitadb/api/query/require/Page.java | 2 +- .../api/query/require/PriceContent.java | 2 +- .../api/query/require/PriceHistogram.java | 2 +- .../evitadb/api/query/require/PriceType.java | 2 +- .../api/query/require/QueryTelemetry.java | 2 +- .../api/query/require/ReferenceContent.java | 2 +- .../io/evitadb/api/query/require/Require.java | 2 +- .../io/evitadb/api/query/require/Strip.java | 2 +- 81 files changed, 522 insertions(+), 81 deletions(-) diff --git a/evita_functional_tests/src/test/java/io/evitadb/documentation/JavaDocCopy.java b/evita_functional_tests/src/test/java/io/evitadb/documentation/JavaDocCopy.java index 9826703d4..ac1164075 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/documentation/JavaDocCopy.java +++ b/evita_functional_tests/src/test/java/io/evitadb/documentation/JavaDocCopy.java @@ -210,6 +210,9 @@ void copyJavaDocToQueryConstraints() throws IOException { ); } + /** + * Run this method to copy user documentation links from {@link ConstraintDefinition} to JavaDoc of {@link Constraint} classes. + */ @Test void copyConstraintUserDocsLinksToJavaDocs() throws URISyntaxException, IOException { final JavaProjectBuilder builder = new JavaProjectBuilder(); @@ -251,11 +254,11 @@ void copyConstraintUserDocsLinksToJavaDocs() throws URISyntaxException, IOExcept final String originalUserDocsLinkLine = constraintSource.get(originalUserDocsLinkLineNumber); if (originalUserDocsLinkLine.contains("Visit detailed user documentation"); + constraintSource.set(originalUserDocsLinkLineNumber, " *

Visit detailed user documentation

"); } else { // there is no link, add it constraintSource.add(originalUserDocsLinkLineNumber + 1, " * Visit detailed user documentation"); - constraintSource.add(originalUserDocsLinkLineNumber + 1, " * "); + constraintSource.add(originalUserDocsLinkLineNumber + 1, " *"); } // rewrite the file with replaced JavaDoc diff --git a/evita_query/src/main/java/io/evitadb/api/query/QueryConstraints.java b/evita_query/src/main/java/io/evitadb/api/query/QueryConstraints.java index b50d05002..e48768b08 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/QueryConstraints.java +++ b/evita_query/src/main/java/io/evitadb/api/query/QueryConstraints.java @@ -68,6 +68,8 @@ public interface QueryConstraints { *
 	 * collection('category')
 	 * 
+ * + *

Visit detailed user documentation

*/ @Nonnull static Collection collection(@Nonnull String entityType) { @@ -94,6 +96,8 @@ static Collection collection(@Nonnull String entityType) { * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static FilterBy filterBy(@Nullable FilterConstraint... constraint) { @@ -119,6 +123,8 @@ static FilterBy filterBy(@Nullable FilterConstraint... constraint) { * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static FilterGroupBy filterGroupBy(@Nullable FilterConstraint... constraint) { @@ -178,6 +184,8 @@ static FilterGroupBy filterGroupBy(@Nullable FilterConstraint... constraint) { * * ... returns a single result - product with entity primary key 106742, which is the only one that all three * `entityPrimaryKeyInSet` constraints have in common. + * + *

Visit detailed user documentation

*/ @Nullable static And and(@Nullable FilterConstraint... constraints) { @@ -240,6 +248,8 @@ static And and(@Nullable FilterConstraint... constraints) { * * ... returns four results representing a combination of all primary keys used in the `entityPrimaryKeyInSet` * constraints. + * + *

Visit detailed user documentation

*/ @Nullable static Or or(@Nullable FilterConstraint... constraints) { @@ -301,6 +311,8 @@ static Or or(@Nullable FilterConstraint... constraints) { * * * ... which returns only three products that were not excluded by the following `not` constraint. + * + *

Visit detailed user documentation

*/ @Nullable static Not not(@Nullable FilterConstraint constraint) { @@ -336,6 +348,8 @@ static Not not(@Nullable FilterConstraint constraint) { * entityPrimaryKeyInSet(1) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceHaving referenceHaving(@Nullable String referenceName, @Nullable FilterConstraint... constraint) { @@ -360,6 +374,8 @@ static ReferenceHaving referenceHaving(@Nullable String referenceName, @Nullable * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static UserFilter userFilter(@Nullable FilterConstraint... constraints) { @@ -413,6 +429,8 @@ static UserFilter userFilter(@Nullable FilterConstraint... constraints) { * between("validity", 0, 1) * between("validity", 6, 7) * + * + *

Visit detailed user documentation

*/ @Nullable static > AttributeBetween attributeBetween(@Nullable String attributeName, @Nullable T from, @Nullable T to) { @@ -444,6 +462,8 @@ static > AttributeBetween attributeBetwee * contains("code","mou") * contains("code","o") * + * + *

Visit detailed user documentation

*/ @Nullable static AttributeContains attributeContains(@Nullable String attributeName, @Nullable String textToSearch) { @@ -472,6 +492,8 @@ static AttributeContains attributeContains(@Nullable String attributeName, @Null * contains("code","mou") * contains("code","do") * + * + *

Visit detailed user documentation

*/ @Nullable static AttributeStartsWith attributeStartsWith(@Nullable String attributeName, @Nullable String textToSearch) { @@ -500,6 +522,8 @@ static AttributeStartsWith attributeStartsWith(@Nullable String attributeName, @ * contains("code","at") * contains("code","og") * + * + *

Visit detailed user documentation

*/ @Nullable static AttributeEndsWith attributeEndsWith(@Nullable String attributeName, @Nullable String textToSearch) { @@ -529,6 +553,8 @@ static AttributeEndsWith attributeEndsWith(@Nullable String attributeName, @Null * equals("code","B") * equals("code","C") * + * + *

Visit detailed user documentation

*/ @Nullable static AttributeEquals attributeEquals(@Nullable String attributeName, @Nullable T attributeValue) { @@ -551,6 +577,8 @@ static AttributeEquals attributeEquals(@Nullable String *
 	 * lessThan("age", 20)
 	 * 
+ * + *

Visit detailed user documentation

*/ @Nullable static > AttributeLessThan attributeLessThan(@Nullable String attributeName, @Nullable T attributeValue) { @@ -574,6 +602,8 @@ static > AttributeLessThan attributeLessT *
 	 * lessThanEquals("age", 20)
 	 * 
+ * + *

Visit detailed user documentation

*/ @Nullable static > AttributeLessThanEquals attributeLessThanEquals(@Nullable String attributeName, @Nullable T attributeValue) { @@ -596,6 +626,8 @@ static > AttributeLessThanEquals attribut *
 	 * greaterThan("age", 20)
 	 * 
+ * + *

Visit detailed user documentation

*/ @Nullable static > AttributeGreaterThan attributeGreaterThan(@Nullable String attributeName, @Nullable T attributeValue) { @@ -619,6 +651,8 @@ static > AttributeGreaterThan attributeGr *
 	 * greaterThanEquals("age", 20)
 	 * 
+ * + *

Visit detailed user documentation

*/ @Nullable static > AttributeGreaterThanEquals attributeGreaterThanEquals(@Nullable String attributeName, @Nullable T attributeValue) { @@ -648,6 +682,8 @@ static > AttributeGreaterThanEquals attri * Warning: Only a single occurrence of any of this constraint is allowed in the filter part of the query. * Currently, there is no way to switch context between different parts of the filter and build queries such as find * a product whose price is either in "CZK" or "EUR" currency at this or that time using this constraint. + * + *

Visit detailed user documentation

*/ @Nullable static PriceInPriceLists priceInPriceLists(@Nullable String... priceList) { @@ -669,6 +705,8 @@ static PriceInPriceLists priceInPriceLists(@Nullable String... priceList) { * Warning: Only a single occurrence of any of this constraint is allowed in the filter part of the query. * Currently, there is no way to switch context between different parts of the filter and build queries such as find * a product whose price is either in "CZK" or "EUR" currency at this or that time using this constraint. + * + *

Visit detailed user documentation

*/ @Nullable static PriceInCurrency priceInCurrency(@Nullable String currency) { @@ -687,6 +725,8 @@ static PriceInCurrency priceInCurrency(@Nullable String currency) { * Warning: Only a single occurrence of any of this constraint is allowed in the filter part of the query. * Currently, there is no way to switch context between different parts of the filter and build queries such as find * a product whose price is either in "CZK" or "EUR" currency at this or that time using this constraint. + * + *

Visit detailed user documentation

*/ @Nullable static PriceInCurrency priceInCurrency(@Nullable Currency currency) { @@ -753,6 +793,8 @@ static PriceInCurrency priceInCurrency(@Nullable Currency currency) { * * Products assigned to two or more subcategories of Accessories category will only appear once in the response * (contrary to what you might expect if you have experience with SQL). + * + *

Visit detailed user documentation

*/ @Nullable static HierarchyWithin hierarchyWithinSelf(@Nullable FilterConstraint ofParent, @Nullable HierarchySpecificationFilterConstraint... with) { @@ -825,6 +867,8 @@ static HierarchyWithin hierarchyWithinSelf(@Nullable FilterConstraint ofParent, * * Products assigned to two or more subcategories of Accessories category will only appear once in the response * (contrary to what you might expect if you have experience with SQL). + * + *

Visit detailed user documentation

*/ @Nullable static HierarchyWithin hierarchyWithin(@Nullable String referenceName, @Nullable FilterConstraint ofParent, @Nullable HierarchySpecificationFilterConstraint... with) { @@ -895,6 +939,8 @@ static HierarchyWithin hierarchyWithin(@Nullable String referenceName, @Nullable * * Products assigned to only one orphan category will be missing from the result. Products assigned to two or more * categories will only appear once in the response (contrary to what you might expect if you have experience with SQL). + * + *

Visit detailed user documentation

*/ @Nonnull static HierarchyWithinRoot hierarchyWithinRootSelf(@Nullable HierarchySpecificationFilterConstraint... with) { @@ -959,6 +1005,8 @@ static HierarchyWithinRoot hierarchyWithinRootSelf(@Nullable HierarchySpecificat * * Products assigned to only one orphan category will be missing from the result. Products assigned to two or more * categories will only appear once in the response (contrary to what you might expect if you have experience with SQL). + * + *

Visit detailed user documentation

*/ @Nonnull static HierarchyWithinRoot hierarchyWithinRoot(@Nullable String referenceName, @Nullable HierarchySpecificationFilterConstraint... with) { @@ -1060,6 +1108,8 @@ static HierarchyWithinRoot hierarchyWithinRoot(@Nullable String referenceName, @ * In the situation where the single product, let's say Garmin Vivosmart 5, is in both the excluded category Christmas * Electronics and the included category Smartwatches, it will remain in the query result because there is at least one * product reference that is part of the visible part of the tree. + * + *

Visit detailed user documentation

*/ @Nullable static HierarchyHaving having(@Nullable FilterConstraint... includeChildTreeConstraints) { @@ -1153,6 +1203,8 @@ static HierarchyHaving having(@Nullable FilterConstraint... includeChildTreeCons * The hierarchical query traverses from the root nodes to the leaf nodes. For each of the nodes, the engine checks * whether the excluding constraint is satisfied valid, and if so, it excludes that hierarchy node and all of its child * nodes (entire subtree). + * + *

Visit detailed user documentation

*/ @Nullable static HierarchyExcluding excluding(@Nullable FilterConstraint... excludeChildTreeConstraints) { @@ -1215,6 +1267,8 @@ static HierarchyExcluding excluding(@Nullable FilterConstraint... excludeChildTr * ) * ) * + * + *

Visit detailed user documentation

*/ @Nonnull static HierarchyDirectRelation directRelation() { @@ -1283,6 +1337,8 @@ static HierarchyDirectRelation directRelation() { * * ... we get only 4 items, which means that 16 were assigned directly to Keyboards category and only 4 of them were * assigned to Exotic keyboards. + * + *

Visit detailed user documentation

*/ @Nonnull static HierarchyExcludingRoot excludingRoot() { @@ -1320,6 +1376,8 @@ static HierarchyExcludingRoot excludingRoot() { * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static EntityLocaleEquals entityLocaleEquals(@Nullable Locale locale) { @@ -1342,6 +1400,8 @@ static EntityLocaleEquals entityLocaleEquals(@Nullable Locale locale) { * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static EntityHaving entityHaving(@Nullable FilterConstraint filterConstraint) { @@ -1373,6 +1433,8 @@ static EntityHaving entityHaving(@Nullable FilterConstraint filterConstraint) { * inRange("age", 24) * inRange("age", 63) * + * + *

Visit detailed user documentation

*/ @Nullable static AttributeInRange attributeInRange(@Nullable String attributeName, @Nullable OffsetDateTime atTheMoment) { @@ -1404,6 +1466,8 @@ static AttributeInRange attributeInRange(@Nullable String attributeName, @Nullab * inRange("age", 24) * inRange("age", 63) * + * + *

Visit detailed user documentation

*/ @Nullable static AttributeInRange attributeInRange(@Nullable String attributeName, @Nullable Number theValue) { @@ -1435,6 +1499,8 @@ static AttributeInRange attributeInRange(@Nullable String attributeName, @Nullab * inRange("age", 24) * inRange("age", 63) * + * + *

Visit detailed user documentation

*/ @Nullable static AttributeInRange attributeInRangeNow(@Nullable String attributeName) { @@ -1465,6 +1531,8 @@ static AttributeInRange attributeInRangeNow(@Nullable String attributeName) { * inSet("code","A","D") * inSet("code","A", "B") * + * + *

Visit detailed user documentation

*/ @SuppressWarnings("unchecked") @Nullable @@ -1509,6 +1577,8 @@ static AttributeInSet attributeInSet(@Nullable String a * equals("code","B") * equals("code","C") * + * + *

Visit detailed user documentation

*/ @Nullable static AttributeEquals attributeEqualsFalse(@Nullable String attributeName) { @@ -1538,6 +1608,8 @@ static AttributeEquals attributeEqualsFalse(@Nullable String attributeName) { * equals("code","B") * equals("code","C") * + * + *

Visit detailed user documentation

*/ @Nullable static AttributeEquals attributeEqualsTrue(@Nullable String attributeName) { @@ -1561,6 +1633,8 @@ static AttributeEquals attributeEqualsTrue(@Nullable String attributeName) { * * * Function supports attribute arrays in the same way as plain values. + * + *

Visit detailed user documentation

*/ @Nullable static AttributeIs attributeIs(@Nullable String attributeName, @Nullable AttributeSpecialValue specialValue) { @@ -1587,6 +1661,8 @@ static AttributeIs attributeIs(@Nullable String attributeName, @Nullable Attribu * * * Function supports attribute arrays in the same way as plain values. + * + *

Visit detailed user documentation

*/ @Nullable static AttributeIs attributeIsNull(@Nullable String attributeName) { @@ -1610,6 +1686,8 @@ static AttributeIs attributeIsNull(@Nullable String attributeName) { * * * Function supports attribute arrays in the same way as plain values. + * + *

Visit detailed user documentation

*/ @Nullable static AttributeIs attributeIsNotNull(@Nullable String attributeName) { @@ -1631,6 +1709,8 @@ static AttributeIs attributeIsNotNull(@Nullable String attributeName) { * Warning: Only a single occurrence of any of this constraint is allowed in the filter part of the query. * Currently, there is no way to switch context between different parts of the filter and build queries such as find * a product whose price is either in "CZK" or "EUR" currency at this or that time using this constraint. + * + *

Visit detailed user documentation

*/ @Nullable static PriceBetween priceBetween(@Nullable BigDecimal from, @Nullable BigDecimal to) { @@ -1654,6 +1734,8 @@ static PriceBetween priceBetween(@Nullable BigDecimal from, @Nullable BigDecimal * Warning: Only a single occurrence of any of this constraint is allowed in the filter part of the query. * Currently, there is no way to switch context between different parts of the filter and build queries such as find * a product whose price is either in "CZK" or "EUR" currency at this or that time using this constraint. + * + *

Visit detailed user documentation

*/ @Nullable static PriceValidIn priceValidIn(@Nullable OffsetDateTime theMoment) { @@ -1673,6 +1755,8 @@ static PriceValidIn priceValidIn(@Nullable OffsetDateTime theMoment) { * Warning: Only a single occurrence of any of this constraint is allowed in the filter part of the query. * Currently, there is no way to switch context between different parts of the filter and build queries such as find * a product whose price is either in "CZK" or "EUR" currency at this or that time using this constraint. + * + *

Visit detailed user documentation

*/ @Nonnull static PriceValidIn priceValidInNow() { @@ -1698,6 +1782,8 @@ static PriceValidIn priceValidInNow() { * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static FacetHaving facetHaving(@Nullable String referenceName, @Nullable FilterConstraint... constraint) { @@ -1713,6 +1799,8 @@ static FacetHaving facetHaving(@Nullable String referenceName, @Nullable FilterC *
 	 * primaryKey(1, 2, 3)
 	 * 
+ * + *

Visit detailed user documentation

*/ @Nullable static EntityPrimaryKeyInSet entityPrimaryKeyInSet(@Nullable Integer... primaryKey) { @@ -1731,6 +1819,8 @@ static EntityPrimaryKeyInSet entityPrimaryKeyInSet(@Nullable Integer... primaryK *
 	 * primaryKey(1, 2, 3)
 	 * 
+ * + *

Visit detailed user documentation

*/ @Nullable static EntityPrimaryKeyInSet entityPrimaryKeyInSet(@Nullable int[] primaryKey) { @@ -1771,6 +1861,8 @@ static EntityPrimaryKeyInSet entityPrimaryKeyInSet(@Nullable int[] primaryKey) { * priceDescending() * ) * + * + *

Visit detailed user documentation

*/ @Nullable static OrderBy orderBy(@Nullable OrderConstraint... constraints) { @@ -1822,6 +1914,8 @@ static OrderBy orderBy(@Nullable OrderConstraint... constraints) { * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static OrderGroupBy orderGroupBy(@Nullable OrderConstraint... constraints) { @@ -1847,6 +1941,8 @@ static OrderGroupBy orderGroupBy(@Nullable OrderConstraint... constraints) { * The example will return the selected entities (if present) in the descending order of their primary keys. Since * the entities are by default ordered by their primary key in ascending order, it has no sense to use this constraint * with {@link OrderDirection#ASC} direction. + * + *

Visit detailed user documentation

*/ @Nonnull static EntityPrimaryKeyNatural entityPrimaryKeyNatural(@Nullable OrderDirection direction) { @@ -1874,6 +1970,8 @@ static EntityPrimaryKeyNatural entityPrimaryKeyNatural(@Nullable OrderDirection * The example will return the selected entities (if present) in the exact order that was used for array filtering them. * The ordering constraint is particularly useful when you have sorted set of entity primary keys from an external * system which needs to be maintained (for example, it represents a relevancy of those entities). + * + *

Visit detailed user documentation

*/ @Nonnull static EntityPrimaryKeyInFilter entityPrimaryKeyInFilter() { @@ -1901,6 +1999,8 @@ static EntityPrimaryKeyInFilter entityPrimaryKeyInFilter() { * this ordering constraint. If there are entities, whose primary keys are not present in the argument, then they * will be present at the end of the output in ascending order of their primary keys (or they will be sorted by * additional ordering constraint in the chain). + * + *

Visit detailed user documentation

*/ @Nullable static EntityPrimaryKeyExact entityPrimaryKeyExact(@Nullable Integer... primaryKey) { @@ -1932,6 +2032,8 @@ static EntityPrimaryKeyExact entityPrimaryKeyExact(@Nullable Integer... primaryK * The example will return the selected entities (if present) in the exact order of their attribute `code` that was used * for array filtering them. The ordering constraint is particularly useful when you have sorted set of attribute values * from an external system which needs to be maintained (for example, it represents a relevancy of those entities). + * + *

Visit detailed user documentation

*/ @Nullable static AttributeSetInFilter attributeSetInFilter(@Nullable String attributeName) { @@ -1962,6 +2064,8 @@ static AttributeSetInFilter attributeSetInFilter(@Nullable String attributeName) * stated in the second to Nth argument of this ordering constraint. If there are entities, that have not the attribute * `code` , then they will be present at the end of the output in ascending order of their primary keys (or they will be * sorted by additional ordering constraint in the chain). + * + *

Visit detailed user documentation

*/ @Nullable static AttributeSetExact attributeSetExact(@Nullable String attributeName, @Nullable Serializable... attributeValues) { @@ -2045,6 +2149,8 @@ static AttributeSetExact attributeSetExact(@Nullable String attributeName, @Null * order them by a property "priority" set on the reference to the category, the first products will be those directly * related to the category, ordered by "priority", followed by the products of the first child category, and so on, * maintaining the depth-first order of the category tree. + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceProperty referenceProperty(@Nullable String referenceName, @Nullable OrderConstraint... constraints) { @@ -2088,6 +2194,8 @@ static ReferenceProperty referenceProperty(@Nullable String referenceName, @Null * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static EntityProperty entityProperty(@Nullable OrderConstraint... constraints) { @@ -2165,6 +2273,8 @@ static EntityProperty entityProperty(@Nullable OrderConstraint... constraints) { * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static EntityGroupProperty entityGroupProperty(@Nullable OrderConstraint... constraints) { @@ -2212,6 +2322,8 @@ static EntityGroupProperty entityGroupProperty(@Nullable OrderConstraint... cons * compound will cover multiple attributes and prepares a special sort index for this particular combination of * attributes, respecting the predefined order and NULL values behaviour. In the query, you can then use the compound * name instead of the default attribute name and achieve the expected results. + * + *

Visit detailed user documentation

*/ @Nullable static AttributeNatural attributeNatural(@Nullable String attributeName) { @@ -2256,6 +2368,8 @@ static AttributeNatural attributeNatural(@Nullable String attributeName) { * compound will cover multiple attributes and prepares a special sort index for this particular combination of * attributes, respecting the predefined order and NULL values behaviour. In the query, you can then use the compound * name instead of the default attribute name and achieve the expected results. + * + *

Visit detailed user documentation

*/ @Nullable static AttributeNatural attributeNatural(@Nullable String attributeName, @Nullable OrderDirection orderDirection) { @@ -2278,6 +2392,8 @@ static AttributeNatural attributeNatural(@Nullable String attributeName, @Nullab * priceNatural() * priceNatural(DESC) * + * + *

Visit detailed user documentation

*/ @Nonnull static PriceNatural priceNatural() { @@ -2299,6 +2415,8 @@ static PriceNatural priceNatural() { * priceNatural() * priceNatural(DESC) * + * + *

Visit detailed user documentation

*/ @Nonnull static PriceNatural priceNatural(@Nullable OrderDirection orderDirection) { @@ -2315,6 +2433,8 @@ static PriceNatural priceNatural(@Nullable OrderDirection orderDirection) { *
 	 * random()
 	 * 
+ * + *

Visit detailed user documentation

*/ @Nonnull static Random random() { @@ -2340,6 +2460,8 @@ static Random random() { * entityFetch() * ) * + * + *

Visit detailed user documentation

*/ @Nullable static Require require(@Nullable RequireConstraint... constraints) { @@ -2361,6 +2483,8 @@ static Require require(@Nullable RequireConstraint... constraints) { *
 	 * attributeHistogram(5, "width", "height")
 	 * 
+ * + *

Visit detailed user documentation

*/ @Nullable static AttributeHistogram attributeHistogram(int requestedBucketCount, @Nullable String... attributeName) { @@ -2384,6 +2508,8 @@ static AttributeHistogram attributeHistogram(int requestedBucketCount, @Nullable *
 	 * priceHistogram(20)
 	 * 
+ * + *

Visit detailed user documentation

*/ @Nonnull static PriceHistogram priceHistogram(int requestedBucketCount) { @@ -2441,6 +2567,8 @@ static PriceHistogram priceHistogram(int requestedBucketCount) { * When user selects facets: blue (11), red (12) by default relation would be: get all entities that have facet blue(11) OR * facet red(12). If require `facetGroupsConjunction('parameterType', 1)` is passed in the query filtering condition will * be composed as: blue(11) AND red(12) + * + *

Visit detailed user documentation

*/ @Nullable static FacetGroupsConjunction facetGroupsConjunction(@Nullable String referenceName, @Nullable FilterBy filterBy) { @@ -2498,6 +2626,8 @@ static FacetGroupsConjunction facetGroupsConjunction(@Nullable String referenceN * When user selects facets: blue (11), red (12) by default relation would be: get all entities that have facet blue(11) OR * facet red(12). If require `facetGroupsConjunction('parameterType', 1)` is passed in the query filtering condition will * be composed as: blue(11) AND red(12) + * + *

Visit detailed user documentation

*/ @Nullable static FacetGroupsConjunction facetGroupsConjunction(@Nullable String referenceName) { @@ -2555,6 +2685,8 @@ static FacetGroupsConjunction facetGroupsConjunction(@Nullable String referenceN * When user selects facets: blue (11), large (22), new products (31) - the default meaning would be: get all entities that * have facet blue as well as facet large and action products tag (AND). If require `facetGroupsDisjunction('tag', 3)` * is passed in the query, filtering condition will be composed as: (`blue(11)` AND `large(22)`) OR `new products(31)` + * + *

Visit detailed user documentation

*/ @Nullable static FacetGroupsDisjunction facetGroupsDisjunction(@Nullable String referenceName, @Nullable FilterBy filterBy) { @@ -2612,6 +2744,8 @@ static FacetGroupsDisjunction facetGroupsDisjunction(@Nullable String referenceN * When user selects facets: blue (11), large (22), new products (31) - the default meaning would be: get all entities that * have facet blue as well as facet large and action products tag (AND). If require `facetGroupsDisjunction('tag', 3)` * is passed in the query, filtering condition will be composed as: (`blue(11)` AND `large(22)`) OR `new products(31)` + * + *

Visit detailed user documentation

*/ @Nullable static FacetGroupsDisjunction facetGroupsDisjunction(@Nullable String referenceName) { @@ -2650,6 +2784,8 @@ static FacetGroupsDisjunction facetGroupsDisjunction(@Nullable String referenceN * The predicted results in the negated groups are far greater than the numbers produced by the default behavior. * Selecting any option in the RAM facet group predicts returning thousands of results, while the ROM facet group with * default behavior predicts only a dozen of them. + * + *

Visit detailed user documentation

*/ @Nullable static FacetGroupsNegation facetGroupsNegation(@Nullable String referenceName, @Nullable FilterBy filterBy) { @@ -2688,6 +2824,8 @@ static FacetGroupsNegation facetGroupsNegation(@Nullable String referenceName, @ * The predicted results in the negated groups are far greater than the numbers produced by the default behavior. * Selecting any option in the RAM facet group predicts returning thousands of results, while the ROM facet group with * default behavior predicts only a dozen of them. + * + *

Visit detailed user documentation

*/ @Nullable static FacetGroupsNegation facetGroupsNegation(@Nullable String referenceName) { @@ -2725,6 +2863,8 @@ static FacetGroupsNegation facetGroupsNegation(@Nullable String referenceName) { * - {@link HierarchySiblings} * - {@link HierarchyChildren} * - {@link HierarchyParents} + * + *

Visit detailed user documentation

*/ @Nullable static HierarchyOfSelf hierarchyOfSelf(@Nullable HierarchyRequireConstraint... requirement) { @@ -2762,6 +2902,8 @@ static HierarchyOfSelf hierarchyOfSelf(@Nullable HierarchyRequireConstraint... r * - {@link HierarchySiblings} * - {@link HierarchyChildren} * - {@link HierarchyParents} + * + *

Visit detailed user documentation

*/ @Nullable static HierarchyOfSelf hierarchyOfSelf( @@ -2806,6 +2948,8 @@ static HierarchyOfSelf hierarchyOfSelf( * - {@link HierarchySiblings} * - {@link HierarchyChildren} * - {@link HierarchyParents} + * + *

Visit detailed user documentation

*/ @Nullable static HierarchyOfReference hierarchyOfReference( @@ -2850,6 +2994,8 @@ static HierarchyOfReference hierarchyOfReference( * - {@link HierarchySiblings} * - {@link HierarchyChildren} * - {@link HierarchyParents} + * + *

Visit detailed user documentation

*/ @Nullable static HierarchyOfReference hierarchyOfReference( @@ -2895,6 +3041,8 @@ static HierarchyOfReference hierarchyOfReference( * - {@link HierarchySiblings} * - {@link HierarchyChildren} * - {@link HierarchyParents} + * + *

Visit detailed user documentation

*/ @Nullable static HierarchyOfReference hierarchyOfReference( @@ -2946,6 +3094,8 @@ static HierarchyOfReference hierarchyOfReference( * - {@link HierarchySiblings} * - {@link HierarchyChildren} * - {@link HierarchyParents} + * + *

Visit detailed user documentation

*/ @Nullable static HierarchyOfReference hierarchyOfReference( @@ -2999,6 +3149,8 @@ static HierarchyOfReference hierarchyOfReference( * - {@link HierarchySiblings} * - {@link HierarchyChildren} * - {@link HierarchyParents} + * + *

Visit detailed user documentation

*/ @Nullable static HierarchyOfReference hierarchyOfReference( @@ -3043,6 +3195,8 @@ static HierarchyOfReference hierarchyOfReference( * - {@link HierarchySiblings} * - {@link HierarchyChildren} * - {@link HierarchyParents} + * + *

Visit detailed user documentation

*/ @Nullable static HierarchyOfReference hierarchyOfReference( @@ -3088,6 +3242,8 @@ static HierarchyOfReference hierarchyOfReference( * - {@link HierarchySiblings} * - {@link HierarchyChildren} * - {@link HierarchyParents} + * + *

Visit detailed user documentation

*/ @Nullable static HierarchyOfReference hierarchyOfReference( @@ -3143,6 +3299,8 @@ static HierarchyOfReference hierarchyOfReference( * - {@link HierarchySiblings} * - {@link HierarchyChildren} * - {@link HierarchyParents} + * + *

Visit detailed user documentation

*/ @Nullable static HierarchyOfReference hierarchyOfReference( @@ -3225,6 +3383,8 @@ static HierarchyOfReference hierarchyOfReference( * the `fromRoot` respects them. The reason is simple: when you render a menu for the query result, you want * the calculated statistics to respect the rules that apply to the {@link HierarchyWithin} so that the calculated * number remains consistent for the end user. + * + *

Visit detailed user documentation

*/ @Nullable static HierarchyFromRoot fromRoot( @@ -3298,6 +3458,8 @@ static HierarchyFromRoot fromRoot( * the `fromRoot` respects them. The reason is simple: when you render a menu for the query result, you want * the calculated statistics to respect the rules that apply to the {@link HierarchyWithin} so that the calculated * number remains consistent for the end user. + * + *

Visit detailed user documentation

*/ @Nullable static HierarchyFromRoot fromRoot( @@ -3390,6 +3552,8 @@ static HierarchyFromRoot fromRoot( * the `fromNode` respects them. The reason is simple: when you render a menu for the query result, you want * the calculated statistics to respect the rules that apply to the hierarchyWithin so that the calculated number * remains consistent for the end user. + * + *

Visit detailed user documentation

*/ @Nullable static HierarchyFromNode fromNode( @@ -3484,6 +3648,8 @@ static HierarchyFromNode fromNode( * the `fromNode` respects them. The reason is simple: when you render a menu for the query result, you want * the calculated statistics to respect the rules that apply to the hierarchyWithin so that the calculated number * remains consistent for the end user. + * + *

Visit detailed user documentation

*/ @Nullable static HierarchyFromNode fromNode( @@ -3556,6 +3722,8 @@ static HierarchyFromNode fromNode( * well. The reason is simple: when you render a menu for the query result, you want the calculated statistics to * respect the rules that apply to the hierarchyWithin so that the calculated number remains consistent for the end * user. + * + *

Visit detailed user documentation

*/ @Nullable static HierarchyChildren children( @@ -3625,6 +3793,8 @@ static HierarchyChildren children( * well. The reason is simple: when you render a menu for the query result, you want the calculated statistics to * respect the rules that apply to the hierarchyWithin so that the calculated number remains consistent for the end * user. + * + *

Visit detailed user documentation

*/ @Nullable static HierarchyChildren children( @@ -3701,6 +3871,8 @@ static HierarchyChildren children( * the first string argument that defines the name for the output data structure. The reason is that this name is * already defined on the enclosing parents constraint, and the siblings constraint simply extends the data available * in its data structure. + * + *

Visit detailed user documentation

*/ @Nullable static HierarchySiblings siblings( @@ -3778,6 +3950,8 @@ static HierarchySiblings siblings( * the first string argument that defines the name for the output data structure. The reason is that this name is * already defined on the enclosing parents constraint, and the siblings constraint simply extends the data available * in its data structure. + * + *

Visit detailed user documentation

*/ @Nullable static HierarchySiblings siblings( @@ -3854,6 +4028,8 @@ static HierarchySiblings siblings( * the first string argument that defines the name for the output data structure. The reason is that this name is * already defined on the enclosing parents constraint, and the siblings constraint simply extends the data available * in its data structure. + * + *

Visit detailed user documentation

*/ @Nullable static HierarchySiblings siblings( @@ -3926,6 +4102,8 @@ static HierarchySiblings siblings( * the first string argument that defines the name for the output data structure. The reason is that this name is * already defined on the enclosing parents constraint, and the siblings constraint simply extends the data available * in its data structure. + * + *

Visit detailed user documentation

*/ @Nullable static HierarchySiblings siblings(@Nullable HierarchyOutputRequireConstraint... requirements) { @@ -3986,6 +4164,8 @@ static HierarchySiblings siblings(@Nullable HierarchyOutputRequireConstraint... * the parents will respect them as well during child nodes / queried entities statistics calculation. The reason is * simple: when you render a menu for the query result, you want the calculated statistics to respect the rules that * apply to the {@link HierarchyWithin} so that the calculated number remains consistent for the end user. + * + *

Visit detailed user documentation

*/ @Nullable static HierarchyParents parents( @@ -4054,6 +4234,8 @@ static HierarchyParents parents( * the parents will respect them as well during child nodes / queried entities statistics calculation. The reason is * simple: when you render a menu for the query result, you want the calculated statistics to respect the rules that * apply to the {@link HierarchyWithin} so that the calculated number remains consistent for the end user. + * + *

Visit detailed user documentation

*/ @Nullable static HierarchyParents parents( @@ -4127,6 +4309,8 @@ static HierarchyParents parents( * the parents will respect them as well during child nodes / queried entities statistics calculation. The reason is * simple: when you render a menu for the query result, you want the calculated statistics to respect the rules that * apply to the {@link HierarchyWithin} so that the calculated number remains consistent for the end user. + * + *

Visit detailed user documentation

*/ @Nullable static HierarchyParents parents( @@ -4194,6 +4378,8 @@ static HierarchyParents parents( * the parents will respect them as well during child nodes / queried entities statistics calculation. The reason is * simple: when you render a menu for the query result, you want the calculated statistics to respect the rules that * apply to the {@link HierarchyWithin} so that the calculated number remains consistent for the end user. + * + *

Visit detailed user documentation

*/ @Nullable static HierarchyParents parents( @@ -4222,6 +4408,8 @@ static HierarchyParents parents( * * which define the constraint that stops traversing the hierarchy tree when it's satisfied by a currently traversed * node. + * + *

Visit detailed user documentation

*/ @Nullable static HierarchyStopAt stopAt(@Nullable HierarchyStopAtRequireConstraint stopConstraint) { @@ -4268,6 +4456,8 @@ static HierarchyStopAt stopAt(@Nullable HierarchyStopAtRequireConstraint stopCon * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static HierarchyNode node(@Nullable FilterBy filterBy) { @@ -4305,6 +4495,8 @@ static HierarchyNode node(@Nullable FilterBy filterBy) { * * The query lists products in Audio category and its subcategories. Along with the products returned, it * also returns a computed megaMenu data structure that lists top two levels of the entire hierarchy. + * + *

Visit detailed user documentation

*/ @Nullable static HierarchyLevel level(@Nullable Integer level) { @@ -4349,6 +4541,8 @@ static HierarchyLevel level(@Nullable Integer level) { * The following query lists products in category Audio and its subcategories. Along with the products returned, it * also returns a computed subcategories data structure that lists the flat category list the currently focused category * Audio. + * + *

Visit detailed user documentation

*/ @Nullable static HierarchyDistance distance(@Nullable Integer distance) { @@ -4396,6 +4590,8 @@ static HierarchyDistance distance(@Nullable Integer distance) { * This query actually has to filter and aggregate all the records in the database, which is obviously quite expensive, * even considering that all the indexes are in-memory. Caching is probably the only way out if you really need * to crunch these numbers. + * + *

Visit detailed user documentation

*/ @Nullable static HierarchyStatistics statistics(@Nullable StatisticsType... type) { @@ -4445,6 +4641,8 @@ static HierarchyStatistics statistics(@Nullable StatisticsType... type) { * This query actually has to filter and aggregate all the records in the database, which is obviously quite expensive, * even considering that all the indexes are in-memory. Caching is probably the only way out if you really need * to crunch these numbers. + * + *

Visit detailed user documentation

*/ @Nullable static HierarchyStatistics statistics(@Nullable StatisticsBase base, @Nullable StatisticsType... type) { @@ -4484,6 +4682,8 @@ static HierarchyStatistics statistics(@Nullable StatisticsBase base, @Nullable S * - {@link PriceContent} * - {@link HierarchyContent} * - {@link ReferenceContent} + * + *

Visit detailed user documentation

*/ @Nonnull static EntityFetch entityFetch(@Nullable EntityContentRequire... requirements) { @@ -4526,6 +4726,8 @@ static EntityFetch entityFetch(@Nullable EntityContentRequire... requirements) { * - {@link PriceContent} * - {@link HierarchyContent} * - {@link ReferenceContent} + * + *

Visit detailed user documentation

*/ @Nonnull static EntityGroupFetch entityGroupFetch(@Nullable EntityContentRequire... requirements) { @@ -4551,6 +4753,8 @@ static EntityGroupFetch entityGroupFetch(@Nullable EntityContentRequire... requi * attributeContent("code", "name") * ) * + * + *

Visit detailed user documentation

*/ @Nonnull static AttributeContent attributeContentAll() { @@ -4573,6 +4777,8 @@ static AttributeContent attributeContentAll() { * attributeContent("code", "name") * ) * + * + *

Visit detailed user documentation

*/ @Nonnull static AttributeContent attributeContent(@Nullable String... attributeName) { @@ -4596,6 +4802,8 @@ static AttributeContent attributeContent(@Nullable String... attributeName) { *
 	 * associatedData("description", "gallery-3d")
 	 * 
+ * + *

Visit detailed user documentation

*/ @Nonnull static AssociatedDataContent associatedDataContentAll() { @@ -4616,6 +4824,8 @@ static AssociatedDataContent associatedDataContentAll() { *
 	 * associatedData("description", "gallery-3d")
 	 * 
+ * + *

Visit detailed user documentation

*/ @Nonnull static AssociatedDataContent associatedDataContent(@Nullable String... associatedDataName) { @@ -4649,6 +4859,8 @@ static AssociatedDataContent associatedDataContent(@Nullable String... associate *
 	 * dataInLocalesAll()
 	 * 
+ * + *

Visit detailed user documentation

*/ @Nonnull static DataInLocales dataInLocalesAll() { @@ -4679,6 +4891,8 @@ static DataInLocales dataInLocalesAll() { *
 	 * dataInLocalesAll()
 	 * 
+ * + *

Visit detailed user documentation

*/ @Nonnull static DataInLocales dataInLocales(@Nullable Locale... locale) { @@ -4783,6 +4997,8 @@ static DataInLocales dataInLocales(@Nullable Locale... locale) { * ) * ) * + * + *

Visit detailed user documentation

*/ @Nonnull static ReferenceContent referenceContentAll() { @@ -4884,6 +5100,8 @@ static ReferenceContent referenceContentAll() { * ) * ) * + * + *

Visit detailed user documentation

*/ @Nonnull static ReferenceContent referenceContentAllWithAttributes() { @@ -4985,6 +5203,8 @@ static ReferenceContent referenceContentAllWithAttributes() { * ) * ) * + * + *

Visit detailed user documentation

*/ @Nonnull static ReferenceContent referenceContentAllWithAttributes(@Nullable AttributeContent attributeContent) { @@ -5087,6 +5307,8 @@ static ReferenceContent referenceContentAllWithAttributes(@Nullable AttributeCon * ) * ) * + * + *

Visit detailed user documentation

*/ @Nonnull static ReferenceContent referenceContent(@Nullable String referenceName) { @@ -5191,6 +5413,8 @@ static ReferenceContent referenceContent(@Nullable String referenceName) { * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName, @Nullable String... attributeNames) { @@ -5299,6 +5523,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName) { @@ -5401,6 +5627,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName, @Nullable AttributeContent attributeContent) { @@ -5505,6 +5733,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nonnull static ReferenceContent referenceContent(@Nullable String... referenceName) { @@ -5609,6 +5839,8 @@ static ReferenceContent referenceContent(@Nullable String... referenceName) { * ) * ) * + * + *

Visit detailed user documentation

*/ @Nonnull static ReferenceContent referenceContent(@Nullable String referenceName, @Nullable EntityFetch entityRequirement) { @@ -5718,6 +5950,8 @@ static ReferenceContent referenceContent(@Nullable String referenceName, @Nullab * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName, @Nullable EntityFetch entityRequirement) { @@ -5822,6 +6056,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName, @Nullable AttributeContent attributeContent, @Nullable EntityFetch entityRequirement) { @@ -5926,6 +6162,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nonnull static ReferenceContent referenceContent(@Nullable String referenceName, @Nullable EntityGroupFetch groupEntityRequirement) { @@ -6033,6 +6271,8 @@ static ReferenceContent referenceContent(@Nullable String referenceName, @Nullab * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName, @Nullable EntityGroupFetch groupEntityRequirement) { @@ -6137,6 +6377,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName, @Nullable AttributeContent attributeContent, @Nullable EntityGroupFetch groupEntityRequirement) { @@ -6241,6 +6483,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nonnull static ReferenceContent referenceContent(@Nullable String referenceName, @Nullable EntityFetch entityRequirement, @Nullable EntityGroupFetch groupEntityRequirement) { @@ -6345,6 +6589,8 @@ static ReferenceContent referenceContent(@Nullable String referenceName, @Nullab * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes( @@ -6451,6 +6697,8 @@ static ReferenceContent referenceContentWithAttributes( * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes( @@ -6559,6 +6807,8 @@ static ReferenceContent referenceContentWithAttributes( * ) * ) * + * + *

Visit detailed user documentation

*/ @Nonnull static ReferenceContent referenceContent(@Nullable String[] referencedEntityTypes, @Nullable EntityFetch entityRequirement) { @@ -6670,6 +6920,8 @@ static ReferenceContent referenceContent(@Nullable String[] referencedEntityType * ) * ) * + * + *

Visit detailed user documentation

*/ @Nonnull static ReferenceContent referenceContent(@Nullable String[] referencedEntityTypes, @Nullable EntityGroupFetch groupEntityRequirement) { @@ -6781,6 +7033,8 @@ static ReferenceContent referenceContent(@Nullable String[] referencedEntityType * ) * ) * + * + *

Visit detailed user documentation

*/ @Nonnull static ReferenceContent referenceContent(@Nullable String[] referencedEntityTypes, @Nullable EntityFetch entityRequirement, @Nullable EntityGroupFetch groupEntityRequirement) { @@ -6886,6 +7140,8 @@ static ReferenceContent referenceContent(@Nullable String[] referencedEntityType * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContent(@Nullable String referenceName, @Nullable FilterBy filterBy) { @@ -6987,6 +7243,8 @@ static ReferenceContent referenceContent(@Nullable String referenceName, @Nullab * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName, @Nullable FilterBy filterBy) { @@ -7088,6 +7346,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName, @Nullable FilterBy filterBy, @Nullable AttributeContent attributeContent) { @@ -7189,6 +7449,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContent(@Nullable String referenceName, @Nullable FilterBy filterBy, @Nullable EntityFetch entityRequirement) { @@ -7290,6 +7552,8 @@ static ReferenceContent referenceContent(@Nullable String referenceName, @Nullab * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName, @Nullable FilterBy filterBy, @Nullable EntityFetch entityRequirement) { @@ -7394,6 +7658,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName, @Nullable FilterBy filterBy, @Nullable AttributeContent attributeContent, @Nullable EntityFetch entityRequirement) { @@ -7498,6 +7764,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContent(@Nullable String referenceName, @Nullable FilterBy filterBy, @Nullable EntityGroupFetch groupEntityRequirement) { @@ -7599,6 +7867,8 @@ static ReferenceContent referenceContent(@Nullable String referenceName, @Nullab * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName, @Nullable FilterBy filterBy, @Nullable EntityGroupFetch groupEntityRequirement) { @@ -7703,6 +7973,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName, @Nullable FilterBy filterBy, @Nullable AttributeContent attributeContent, @Nullable EntityGroupFetch groupEntityRequirement) { @@ -7807,6 +8079,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContent(@Nullable String referenceName, @Nullable FilterBy filterBy, @Nullable EntityFetch entityRequirement, @Nullable EntityGroupFetch groupEntityRequirement) { @@ -7908,6 +8182,8 @@ static ReferenceContent referenceContent(@Nullable String referenceName, @Nullab * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName, @Nullable FilterBy filterBy, @Nullable EntityFetch entityRequirement, @Nullable EntityGroupFetch groupEntityRequirement) { @@ -8012,6 +8288,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName, @Nullable FilterBy filterBy, @Nullable AttributeContent attributeContent, @Nullable EntityFetch entityRequirement, @Nullable EntityGroupFetch groupEntityRequirement) { @@ -8116,6 +8394,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContent(@Nullable String referenceName, @Nullable OrderBy orderBy) { @@ -8217,6 +8497,8 @@ static ReferenceContent referenceContent(@Nullable String referenceName, @Nullab * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName, @Nullable OrderBy orderBy) { @@ -8321,6 +8603,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName, @Nullable OrderBy orderBy, @Nullable AttributeContent attributeContent) { @@ -8425,6 +8709,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContent(@Nullable String referenceName, @Nullable OrderBy orderBy, @Nullable EntityFetch entityRequirement) { @@ -8526,6 +8812,8 @@ static ReferenceContent referenceContent(@Nullable String referenceName, @Nullab * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName, @Nullable OrderBy orderBy, @Nullable EntityFetch entityRequirement) { @@ -8630,6 +8918,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName, @Nullable OrderBy orderBy, @Nullable AttributeContent attributeContent, @Nullable EntityFetch entityRequirement) { @@ -8734,6 +9024,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContent(@Nullable String referenceName, @Nullable OrderBy orderBy, @Nullable EntityGroupFetch groupEntityRequirement) { @@ -8835,6 +9127,8 @@ static ReferenceContent referenceContent(@Nullable String referenceName, @Nullab * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName, @Nullable OrderBy orderBy, @Nullable EntityGroupFetch groupEntityRequirement) { @@ -8939,6 +9233,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName, @Nullable OrderBy orderBy, @Nullable AttributeContent attributeContent, @Nullable EntityGroupFetch groupEntityRequirement) { @@ -9043,6 +9339,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContent(@Nullable String referenceName, @Nullable OrderBy orderBy, @Nullable EntityFetch entityRequirement, @Nullable EntityGroupFetch groupEntityRequirement) { @@ -9144,6 +9442,8 @@ static ReferenceContent referenceContent(@Nullable String referenceName, @Nullab * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName, @Nullable OrderBy orderBy, @Nullable EntityFetch entityRequirement, @Nullable EntityGroupFetch groupEntityRequirement) { @@ -9248,6 +9548,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName, @Nullable OrderBy orderBy, @Nullable AttributeContent attributeContent, @Nullable EntityFetch entityRequirement, @Nullable EntityGroupFetch groupEntityRequirement) { @@ -9352,6 +9654,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContent(@Nullable String referenceName, @Nullable FilterBy filterBy, @Nullable OrderBy orderBy) { @@ -9453,6 +9757,8 @@ static ReferenceContent referenceContent(@Nullable String referenceName, @Nullab * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName, @Nullable FilterBy filterBy, @Nullable OrderBy orderBy) { @@ -9557,6 +9863,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName, @Nullable FilterBy filterBy, @Nullable OrderBy orderBy, @Nullable AttributeContent attributeContent) { @@ -9661,6 +9969,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContent(@Nullable String referenceName, @Nullable FilterBy filterBy, @Nullable OrderBy orderBy, @Nullable EntityFetch entityRequirement) { @@ -9762,6 +10072,8 @@ static ReferenceContent referenceContent(@Nullable String referenceName, @Nullab * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName, @Nullable FilterBy filterBy, @Nullable OrderBy orderBy, @Nullable EntityFetch entityRequirement) { @@ -9866,6 +10178,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName, @Nullable FilterBy filterBy, @Nullable OrderBy orderBy, @Nullable AttributeContent attributeContent, @Nullable EntityFetch entityRequirement) { @@ -9970,6 +10284,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContent(@Nullable String referenceName, @Nullable FilterBy filterBy, @Nullable OrderBy orderBy, @Nullable EntityGroupFetch groupEntityRequirement) { @@ -10071,6 +10387,8 @@ static ReferenceContent referenceContent(@Nullable String referenceName, @Nullab * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName, @Nullable FilterBy filterBy, @Nullable OrderBy orderBy, @Nullable EntityGroupFetch groupEntityRequirement) { @@ -10175,6 +10493,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName, @Nullable FilterBy filterBy, @Nullable OrderBy orderBy, @Nullable AttributeContent attributeContent, @Nullable EntityGroupFetch groupEntityRequirement) { @@ -10279,6 +10599,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContent(@Nullable String referenceName, @Nullable FilterBy filterBy, @Nullable OrderBy orderBy, @Nullable EntityFetch entityRequirement, @Nullable EntityGroupFetch groupEntityRequirement) { @@ -10380,6 +10702,8 @@ static ReferenceContent referenceContent(@Nullable String referenceName, @Nullab * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName, @Nullable FilterBy filterBy, @Nullable OrderBy orderBy, @Nullable EntityFetch entityRequirement, @Nullable EntityGroupFetch groupEntityRequirement) { @@ -10484,6 +10808,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nullable static ReferenceContent referenceContentWithAttributes(@Nullable String referenceName, @Nullable FilterBy filterBy, @Nullable OrderBy orderBy, @Nullable AttributeContent attributeContent, @Nullable EntityFetch entityRequirement, @Nullable EntityGroupFetch groupEntityRequirement) { @@ -10588,6 +10914,8 @@ static ReferenceContent referenceContentWithAttributes(@Nullable String referenc * ) * ) * + * + *

Visit detailed user documentation

*/ @Nonnull static ReferenceContent referenceContentAll(@Nullable EntityFetch entityRequirement) { @@ -10689,6 +11017,8 @@ static ReferenceContent referenceContentAll(@Nullable EntityFetch entityRequirem * ) * ) * + * + *

Visit detailed user documentation

*/ @Nonnull static ReferenceContent referenceContentAllWithAttributes(@Nullable EntityFetch entityRequirement) { @@ -10790,6 +11120,8 @@ static ReferenceContent referenceContentAllWithAttributes(@Nullable EntityFetch * ) * ) * + * + *

Visit detailed user documentation

*/ @Nonnull static ReferenceContent referenceContentAllWithAttributes(@Nullable AttributeContent attributeContent, @Nullable EntityFetch entityRequirement) { @@ -10891,6 +11223,8 @@ static ReferenceContent referenceContentAllWithAttributes(@Nullable AttributeCon * ) * ) * + * + *

Visit detailed user documentation

*/ @Nonnull static ReferenceContent referenceContentAll(@Nullable EntityGroupFetch groupEntityRequirement) { @@ -10992,6 +11326,8 @@ static ReferenceContent referenceContentAll(@Nullable EntityGroupFetch groupEnti * ) * ) * + * + *

Visit detailed user documentation

*/ @Nonnull static ReferenceContent referenceContentAllWithAttributes(@Nullable EntityGroupFetch groupEntityRequirement) { @@ -11093,6 +11429,8 @@ static ReferenceContent referenceContentAllWithAttributes(@Nullable EntityGroupF * ) * ) * + * + *

Visit detailed user documentation

*/ @Nonnull static ReferenceContent referenceContentAllWithAttributes(@Nullable AttributeContent attributeContent, @Nullable EntityGroupFetch groupEntityRequirement) { @@ -11194,6 +11532,8 @@ static ReferenceContent referenceContentAllWithAttributes(@Nullable AttributeCon * ) * ) * + * + *

Visit detailed user documentation

*/ @Nonnull static ReferenceContent referenceContentAll(@Nullable EntityFetch entityRequirement, @Nullable EntityGroupFetch groupEntityRequirement) { @@ -11295,6 +11635,8 @@ static ReferenceContent referenceContentAll(@Nullable EntityFetch entityRequirem * ) * ) * + * + *

Visit detailed user documentation

*/ @Nonnull static ReferenceContent referenceContentAllWithAttributes(@Nullable EntityFetch entityRequirement, @Nullable EntityGroupFetch groupEntityRequirement) { @@ -11396,6 +11738,8 @@ static ReferenceContent referenceContentAllWithAttributes(@Nullable EntityFetch * ) * ) * + * + *

Visit detailed user documentation

*/ @Nonnull static ReferenceContent referenceContentAllWithAttributes(@Nullable AttributeContent attributeContent, @Nullable EntityFetch entityRequirement, @Nullable EntityGroupFetch groupEntityRequirement) { @@ -11424,6 +11768,8 @@ static ReferenceContent referenceContentAllWithAttributes(@Nullable AttributeCon * hierarchyContent() * ) * + * + *

Visit detailed user documentation

*/ @Nonnull static HierarchyContent hierarchyContent() { @@ -11452,6 +11798,8 @@ static HierarchyContent hierarchyContent() { * hierarchyContent() * ) * + * + *

Visit detailed user documentation

*/ @Nonnull static HierarchyContent hierarchyContent(@Nullable HierarchyStopAt stopAt) { @@ -11480,6 +11828,8 @@ static HierarchyContent hierarchyContent(@Nullable HierarchyStopAt stopAt) { * hierarchyContent() * ) * + * + *

Visit detailed user documentation

*/ @Nonnull static HierarchyContent hierarchyContent(@Nullable EntityFetch entityFetch) { @@ -11508,6 +11858,8 @@ static HierarchyContent hierarchyContent(@Nullable EntityFetch entityFetch) { * hierarchyContent() * ) * + * + *

Visit detailed user documentation

*/ @Nonnull static HierarchyContent hierarchyContent(@Nullable HierarchyStopAt stopAt, @Nullable EntityFetch entityFetch) { @@ -11541,6 +11893,8 @@ static HierarchyContent hierarchyContent(@Nullable HierarchyStopAt stopAt, @Null * priceContentRespectingFilter("reference") * priceContentAll() * + * + *

Visit detailed user documentation

*/ @Nullable static PriceContent priceContent(@Nullable PriceContentMode contentMode, @Nullable String... priceLists) { @@ -11575,6 +11929,8 @@ static PriceContent priceContent(@Nullable PriceContentMode contentMode, @Nullab * priceContentRespectingFilter("reference") * priceContentAll() * + * + *

Visit detailed user documentation

*/ @Nonnull static PriceContent priceContentAll() { @@ -11602,6 +11958,8 @@ static PriceContent priceContentAll() { * priceContentRespectingFilter("reference") * priceContentAll() * + * + *

Visit detailed user documentation

*/ @Nonnull static PriceContent priceContentRespectingFilter(@Nullable String... priceLists) { @@ -11624,6 +11982,8 @@ static PriceContent priceContentRespectingFilter(@Nullable String... priceLists) * ``` * useOfPrice(WITH_TAX) * ``` + * + *

Visit detailed user documentation

*/ @Nonnull static PriceType priceType(@Nullable QueryPriceMode priceMode) { @@ -11646,6 +12006,8 @@ static PriceType priceType(@Nullable QueryPriceMode priceMode) { *
 	 * page(1, 24)
 	 * 
+ * + *

Visit detailed user documentation

*/ @Nonnull static Page page(@Nullable Integer pageNumber, @Nullable Integer pageSize) { @@ -11668,6 +12030,8 @@ static Page page(@Nullable Integer pageNumber, @Nullable Integer pageSize) { *
 	 * strip(52, 24)
 	 * 
+ * + *

Visit detailed user documentation

*/ @Nonnull static Strip strip(@Nullable Integer offset, @Nullable Integer limit) { @@ -11753,6 +12117,8 @@ static Strip strip(@Nullable Integer offset, @Nullable Integer limit) { * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nonnull static FacetSummary facetSummary() { @@ -11838,6 +12204,8 @@ static FacetSummary facetSummary() { * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nonnull static FacetSummary facetSummary(@Nullable FacetStatisticsDepth statisticsDepth, @Nullable EntityRequire... requirements) { @@ -11925,6 +12293,8 @@ static FacetSummary facetSummary(@Nullable FacetStatisticsDepth statisticsDepth, * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nonnull static FacetSummary facetSummary( @@ -12015,6 +12385,8 @@ static FacetSummary facetSummary( * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nonnull static FacetSummary facetSummary( @@ -12106,6 +12478,8 @@ static FacetSummary facetSummary( * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nonnull static FacetSummary facetSummary( @@ -12197,6 +12571,8 @@ static FacetSummary facetSummary( * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nonnull static FacetSummary facetSummary( @@ -12288,6 +12664,8 @@ static FacetSummary facetSummary( * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nonnull static FacetSummary facetSummary( @@ -12379,6 +12757,8 @@ static FacetSummary facetSummary( * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nonnull static FacetSummary facetSummary( @@ -12469,6 +12849,8 @@ static FacetSummary facetSummary( * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nonnull static FacetSummary facetSummary( @@ -12559,6 +12941,8 @@ static FacetSummary facetSummary( * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nonnull static FacetSummary facetSummary( @@ -12648,6 +13032,8 @@ static FacetSummary facetSummary( * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nonnull static FacetSummary facetSummary( @@ -12737,6 +13123,8 @@ static FacetSummary facetSummary( * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nonnull static FacetSummary facetSummary( @@ -12826,6 +13214,8 @@ static FacetSummary facetSummary( * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nonnull static FacetSummary facetSummary( @@ -12915,6 +13305,8 @@ static FacetSummary facetSummary( * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nonnull static FacetSummary facetSummary( @@ -13005,6 +13397,8 @@ static FacetSummary facetSummary( * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nonnull static FacetSummary facetSummary( @@ -13095,6 +13489,8 @@ static FacetSummary facetSummary( * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nonnull static FacetSummary facetSummary( @@ -13185,6 +13581,8 @@ static FacetSummary facetSummary( * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nonnull static FacetSummary facetSummary( @@ -13284,6 +13682,8 @@ static FacetSummary facetSummary( * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nullable static FacetSummaryOfReference facetSummaryOfReference(@Nullable String referenceName, @Nullable EntityRequire... requirements) { @@ -13361,6 +13761,8 @@ static FacetSummaryOfReference facetSummaryOfReference(@Nullable String referenc * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nullable static FacetSummaryOfReference facetSummaryOfReference(@Nullable String referenceName, @Nullable FacetStatisticsDepth statisticsDepth, @Nullable EntityRequire... requirements) { @@ -13443,6 +13845,8 @@ static FacetSummaryOfReference facetSummaryOfReference(@Nullable String referenc * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nullable static FacetSummaryOfReference facetSummaryOfReference( @@ -13527,6 +13931,8 @@ static FacetSummaryOfReference facetSummaryOfReference( * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nullable static FacetSummaryOfReference facetSummaryOfReference( @@ -13612,6 +14018,8 @@ static FacetSummaryOfReference facetSummaryOfReference( * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nullable static FacetSummaryOfReference facetSummaryOfReference( @@ -13697,6 +14105,8 @@ static FacetSummaryOfReference facetSummaryOfReference( * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nullable static FacetSummaryOfReference facetSummaryOfReference( @@ -13782,6 +14192,8 @@ static FacetSummaryOfReference facetSummaryOfReference( * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nullable static FacetSummaryOfReference facetSummaryOfReference( @@ -13867,6 +14279,8 @@ static FacetSummaryOfReference facetSummaryOfReference( * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nullable static FacetSummaryOfReference facetSummaryOfReference( @@ -13951,6 +14365,8 @@ static FacetSummaryOfReference facetSummaryOfReference( * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nullable static FacetSummaryOfReference facetSummaryOfReference( @@ -14035,6 +14451,8 @@ static FacetSummaryOfReference facetSummaryOfReference( * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nullable static FacetSummaryOfReference facetSummaryOfReference( @@ -14118,6 +14536,8 @@ static FacetSummaryOfReference facetSummaryOfReference( * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nullable static FacetSummaryOfReference facetSummaryOfReference( @@ -14201,6 +14621,8 @@ static FacetSummaryOfReference facetSummaryOfReference( * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nullable static FacetSummaryOfReference facetSummaryOfReference( @@ -14284,6 +14706,8 @@ static FacetSummaryOfReference facetSummaryOfReference( * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nullable static FacetSummaryOfReference facetSummaryOfReference( @@ -14367,6 +14791,8 @@ static FacetSummaryOfReference facetSummaryOfReference( * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nullable static FacetSummaryOfReference facetSummaryOfReference( @@ -14451,6 +14877,8 @@ static FacetSummaryOfReference facetSummaryOfReference( * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nullable static FacetSummaryOfReference facetSummaryOfReference( @@ -14535,6 +14963,8 @@ static FacetSummaryOfReference facetSummaryOfReference( * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nullable static FacetSummaryOfReference facetSummaryOfReference( @@ -14619,6 +15049,8 @@ static FacetSummaryOfReference facetSummaryOfReference( * * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. + * + *

Visit detailed user documentation

*/ @Nullable static FacetSummaryOfReference facetSummaryOfReference( @@ -14661,6 +15093,8 @@ static FacetSummaryOfReference facetSummaryOfReference( *
 	 * queryTelemetry()
 	 * 
+ * + *

Visit detailed user documentation

*/ @Nonnull static QueryTelemetry queryTelemetry() { @@ -14704,6 +15138,8 @@ static Debug debug(@Nullable DebugMode... debugMode) { * - {@link PriceContent} * - {@link HierarchyContent} * - {@link ReferenceContent} + * + *

Visit detailed user documentation

*/ @Nonnull static EntityFetch entityFetchAll() { @@ -14747,6 +15183,8 @@ static EntityFetch entityFetchAll() { * - {@link PriceContent} * - {@link HierarchyContent} * - {@link ReferenceContent} + * + *

Visit detailed user documentation

*/ @Nonnull static EntityGroupFetch entityGroupFetchAll() { diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/And.java b/evita_query/src/main/java/io/evitadb/api/query/filter/And.java index 9743574f9..8347ff9d3 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/And.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/And.java @@ -88,7 +88,7 @@ * ... returns a single result - product with entity primary key 106742, which is the only one that all three * `entityPrimaryKeyInSet` constraints have in common. * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný, FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeBetween.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeBetween.java index b347fd376..1a0efd97d 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeBetween.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeBetween.java @@ -81,7 +81,7 @@ * between("validity", 6, 7) * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeContains.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeContains.java index dced31813..372d4e0fc 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeContains.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeContains.java @@ -56,7 +56,7 @@ * contains("code","o") * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeEndsWith.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeEndsWith.java index 476ffa7d7..57e6fefbb 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeEndsWith.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeEndsWith.java @@ -57,7 +57,7 @@ * contains("code","og") * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeEquals.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeEquals.java index 0e6470043..9d8845dd7 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeEquals.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeEquals.java @@ -58,7 +58,7 @@ * equals("code","C") * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeGreaterThan.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeGreaterThan.java index af895c3e3..a92c84cd6 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeGreaterThan.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeGreaterThan.java @@ -52,7 +52,7 @@ * greaterThan("age", 20) * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeGreaterThanEquals.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeGreaterThanEquals.java index f88faaa92..76594a2b3 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeGreaterThanEquals.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeGreaterThanEquals.java @@ -53,7 +53,7 @@ * greaterThanEquals("age", 20) * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeInRange.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeInRange.java index f1450eac8..f8800f1c0 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeInRange.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeInRange.java @@ -73,7 +73,7 @@ * inRange("age", 63) * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeInSet.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeInSet.java index 780664537..dca1cae53 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeInSet.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeInSet.java @@ -60,7 +60,7 @@ * inSet("code","A", "B") * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeIs.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeIs.java index adbfe5446..646e7452e 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeIs.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeIs.java @@ -52,7 +52,7 @@ * * Function supports attribute arrays in the same way as plain values. * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @see AttributeSpecialValue * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeLessThan.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeLessThan.java index d694daebb..078c9da8e 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeLessThan.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeLessThan.java @@ -52,7 +52,7 @@ * lessThan("age", 20) * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeLessThanEquals.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeLessThanEquals.java index ea6e733d8..3c7af12d6 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeLessThanEquals.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeLessThanEquals.java @@ -53,7 +53,7 @@ * lessThanEquals("age", 20) * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeStartsWith.java b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeStartsWith.java index 889d56277..f60f71d47 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeStartsWith.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/AttributeStartsWith.java @@ -57,7 +57,7 @@ * contains("code","do") * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/EntityHaving.java b/evita_query/src/main/java/io/evitadb/api/query/filter/EntityHaving.java index 75fc2b240..97bb5553a 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/EntityHaving.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/EntityHaving.java @@ -52,7 +52,7 @@ * ) * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Lukáš Hornych, FG Forrest a.s. (c) 2022 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/EntityLocaleEquals.java b/evita_query/src/main/java/io/evitadb/api/query/filter/EntityLocaleEquals.java index b8d8b319d..66560cb16 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/EntityLocaleEquals.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/EntityLocaleEquals.java @@ -66,7 +66,7 @@ * ) * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/EntityPrimaryKeyInSet.java b/evita_query/src/main/java/io/evitadb/api/query/filter/EntityPrimaryKeyInSet.java index ea641ec6d..f6e40405f 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/EntityPrimaryKeyInSet.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/EntityPrimaryKeyInSet.java @@ -44,7 +44,7 @@ * primaryKey(1, 2, 3) * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/FacetHaving.java b/evita_query/src/main/java/io/evitadb/api/query/filter/FacetHaving.java index c22918471..ada66124d 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/FacetHaving.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/FacetHaving.java @@ -57,7 +57,7 @@ * ) * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/FilterBy.java b/evita_query/src/main/java/io/evitadb/api/query/filter/FilterBy.java index c22b416d7..f35f30083 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/FilterBy.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/FilterBy.java @@ -53,7 +53,7 @@ * ) * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný, FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/FilterGroupBy.java b/evita_query/src/main/java/io/evitadb/api/query/filter/FilterGroupBy.java index 33ce3f060..2a84d9cf4 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/FilterGroupBy.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/FilterGroupBy.java @@ -58,7 +58,7 @@ * ) * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný, FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyDirectRelation.java b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyDirectRelation.java index 1e6834fc6..476c6de38 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyDirectRelation.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyDirectRelation.java @@ -86,7 +86,7 @@ * ) * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyExcluding.java b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyExcluding.java index 8bb6f2611..26c84b07e 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyExcluding.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyExcluding.java @@ -121,7 +121,7 @@ * whether the excluding constraint is satisfied valid, and if so, it excludes that hierarchy node and all of its child * nodes (entire subtree). * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyExcludingRoot.java b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyExcludingRoot.java index 2215684b9..af999057d 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyExcludingRoot.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyExcludingRoot.java @@ -95,7 +95,7 @@ * ... we get only 4 items, which means that 16 were assigned directly to Keyboards category and only 4 of them were * assigned to Exotic keyboards. * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyHaving.java b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyHaving.java index 056c8a5ac..66db12d8f 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyHaving.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyHaving.java @@ -132,7 +132,7 @@ * Electronics and the included category Smartwatches, it will remain in the query result because there is at least one * product reference that is part of the visible part of the tree. * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Lukáš Hornych, FG Forrest a.s. (c) 2023 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyWithin.java b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyWithin.java index 4085ddd6a..726e351ea 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyWithin.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyWithin.java @@ -108,7 +108,7 @@ * Products assigned to two or more subcategories of Accessories category will only appear once in the response * (contrary to what you might expect if you have experience with SQL). * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyWithinRoot.java b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyWithinRoot.java index 793671cfa..3fd598fd8 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyWithinRoot.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/HierarchyWithinRoot.java @@ -105,7 +105,7 @@ * Products assigned to only one orphan category will be missing from the result. Products assigned to two or more * categories will only appear once in the response (contrary to what you might expect if you have experience with SQL). * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/Not.java b/evita_query/src/main/java/io/evitadb/api/query/filter/Not.java index e632b7d7b..7dce82d99 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/Not.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/Not.java @@ -87,7 +87,7 @@ * * ... which returns only three products that were not excluded by the following `not` constraint. * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný, FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/Or.java b/evita_query/src/main/java/io/evitadb/api/query/filter/Or.java index 8fa2081a2..9cc7bfbc2 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/Or.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/Or.java @@ -88,7 +88,7 @@ * ... returns four results representing a combination of all primary keys used in the `entityPrimaryKeyInSet` * constraints. * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný, FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/PriceBetween.java b/evita_query/src/main/java/io/evitadb/api/query/filter/PriceBetween.java index 3f13fc257..adb6e8289 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/PriceBetween.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/PriceBetween.java @@ -51,7 +51,7 @@ * Currently, there is no way to switch context between different parts of the filter and build queries such as find * a product whose price is either in "CZK" or "EUR" currency at this or that time using this constraint. * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/PriceInCurrency.java b/evita_query/src/main/java/io/evitadb/api/query/filter/PriceInCurrency.java index 4afbe22dd..933e8a167 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/PriceInCurrency.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/PriceInCurrency.java @@ -47,7 +47,7 @@ * Currently, there is no way to switch context between different parts of the filter and build queries such as find * a product whose price is either in "CZK" or "EUR" currency at this or that time using this constraint. * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/PriceInPriceLists.java b/evita_query/src/main/java/io/evitadb/api/query/filter/PriceInPriceLists.java index 99893ffce..cde0e10a6 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/PriceInPriceLists.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/PriceInPriceLists.java @@ -58,7 +58,7 @@ * Currently, there is no way to switch context between different parts of the filter and build queries such as find * a product whose price is either in "CZK" or "EUR" currency at this or that time using this constraint. * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/PriceValidIn.java b/evita_query/src/main/java/io/evitadb/api/query/filter/PriceValidIn.java index d8062624c..c2761b574 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/PriceValidIn.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/PriceValidIn.java @@ -54,7 +54,7 @@ * Currently, there is no way to switch context between different parts of the filter and build queries such as find * a product whose price is either in "CZK" or "EUR" currency at this or that time using this constraint. * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/ReferenceHaving.java b/evita_query/src/main/java/io/evitadb/api/query/filter/ReferenceHaving.java index 329cba635..b3b507caa 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/ReferenceHaving.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/ReferenceHaving.java @@ -66,7 +66,7 @@ * ) * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/filter/UserFilter.java b/evita_query/src/main/java/io/evitadb/api/query/filter/UserFilter.java index 737669398..e3d782b7f 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/filter/UserFilter.java +++ b/evita_query/src/main/java/io/evitadb/api/query/filter/UserFilter.java @@ -60,7 +60,7 @@ * ) * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný, FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/head/Collection.java b/evita_query/src/main/java/io/evitadb/api/query/head/Collection.java index ff65377c8..7b25c3ddd 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/head/Collection.java +++ b/evita_query/src/main/java/io/evitadb/api/query/head/Collection.java @@ -45,7 +45,7 @@ * collection('category') * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/AttributeNatural.java b/evita_query/src/main/java/io/evitadb/api/query/order/AttributeNatural.java index 02b21e725..73880cefd 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/AttributeNatural.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/AttributeNatural.java @@ -76,7 +76,7 @@ * attributes, respecting the predefined order and NULL values behaviour. In the query, you can then use the compound * name instead of the default attribute name and achieve the expected results. * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Lukáš Hornych, FG Forrest a.s. (c) 2022 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/AttributeSetExact.java b/evita_query/src/main/java/io/evitadb/api/query/order/AttributeSetExact.java index 3df96f0d7..f256425e4 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/AttributeSetExact.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/AttributeSetExact.java @@ -59,7 +59,7 @@ * `code` , then they will be present at the end of the output in ascending order of their primary keys (or they will be * sorted by additional ordering constraint in the chain). * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/AttributeSetInFilter.java b/evita_query/src/main/java/io/evitadb/api/query/order/AttributeSetInFilter.java index ebb075062..0ce3872b4 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/AttributeSetInFilter.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/AttributeSetInFilter.java @@ -60,7 +60,7 @@ * for array filtering them. The ordering constraint is particularly useful when you have sorted set of attribute values * from an external system which needs to be maintained (for example, it represents a relevancy of those entities). * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/EntityGroupProperty.java b/evita_query/src/main/java/io/evitadb/api/query/order/EntityGroupProperty.java index 860b463cc..a9753eaf9 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/EntityGroupProperty.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/EntityGroupProperty.java @@ -105,7 +105,7 @@ * ) * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyExact.java b/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyExact.java index d961cbdce..9ac3ab5f9 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyExact.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyExact.java @@ -56,7 +56,7 @@ * will be present at the end of the output in ascending order of their primary keys (or they will be sorted by * additional ordering constraint in the chain). * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyInFilter.java b/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyInFilter.java index bb9842222..c6c366c16 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyInFilter.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyInFilter.java @@ -58,7 +58,7 @@ * The ordering constraint is particularly useful when you have sorted set of entity primary keys from an external * system which needs to be maintained (for example, it represents a relevancy of those entities). * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyNatural.java b/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyNatural.java index 8d3462e46..b6c21da20 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyNatural.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/EntityPrimaryKeyNatural.java @@ -50,7 +50,7 @@ * the entities are by default ordered by their primary key in ascending order, it has no sense to use this constraint * with {@link OrderDirection#ASC} direction. * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/EntityProperty.java b/evita_query/src/main/java/io/evitadb/api/query/order/EntityProperty.java index 969c57236..f9750e456 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/EntityProperty.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/EntityProperty.java @@ -71,7 +71,7 @@ * ) * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/OrderBy.java b/evita_query/src/main/java/io/evitadb/api/query/order/OrderBy.java index 270d24b43..af4d20aa1 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/OrderBy.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/OrderBy.java @@ -63,7 +63,7 @@ * ) * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný, FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/OrderGroupBy.java b/evita_query/src/main/java/io/evitadb/api/query/order/OrderGroupBy.java index 1d0886bac..a6cc41225 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/OrderGroupBy.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/OrderGroupBy.java @@ -80,7 +80,7 @@ * ) * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný, FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/PriceNatural.java b/evita_query/src/main/java/io/evitadb/api/query/order/PriceNatural.java index 3bf0cad39..6c0b8e635 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/PriceNatural.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/PriceNatural.java @@ -50,7 +50,7 @@ * priceNatural(DESC) * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Lukáš Hornych, FG Forrest a.s. (c) 2022 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/Random.java b/evita_query/src/main/java/io/evitadb/api/query/order/Random.java index ef469e783..1301ff9bc 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/Random.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/Random.java @@ -44,7 +44,7 @@ * random() * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/order/ReferenceProperty.java b/evita_query/src/main/java/io/evitadb/api/query/order/ReferenceProperty.java index 137260fba..c50ef9205 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/order/ReferenceProperty.java +++ b/evita_query/src/main/java/io/evitadb/api/query/order/ReferenceProperty.java @@ -111,7 +111,7 @@ * related to the category, ordered by "priority", followed by the products of the first child category, and so on, * maintaining the depth-first order of the category tree. * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/AssociatedDataContent.java b/evita_query/src/main/java/io/evitadb/api/query/require/AssociatedDataContent.java index 852a09f12..99d309129 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/AssociatedDataContent.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/AssociatedDataContent.java @@ -59,7 +59,7 @@ * associatedData("description", "gallery-3d") * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/AttributeContent.java b/evita_query/src/main/java/io/evitadb/api/query/require/AttributeContent.java index f1918a062..bf7660c8b 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/AttributeContent.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/AttributeContent.java @@ -63,7 +63,7 @@ * ) * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/AttributeHistogram.java b/evita_query/src/main/java/io/evitadb/api/query/require/AttributeHistogram.java index 9b9dcc312..1ad9b13f3 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/AttributeHistogram.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/AttributeHistogram.java @@ -51,7 +51,7 @@ * attributeHistogram(5, "width", "height") * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/DataInLocales.java b/evita_query/src/main/java/io/evitadb/api/query/require/DataInLocales.java index 2e62f0422..2197d658c 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/DataInLocales.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/DataInLocales.java @@ -66,7 +66,7 @@ * dataInLocalesAll() * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/EntityFetch.java b/evita_query/src/main/java/io/evitadb/api/query/require/EntityFetch.java index 3ad3dc396..8a0d7c1b5 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/EntityFetch.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/EntityFetch.java @@ -68,7 +68,7 @@ * - {@link HierarchyContent} * - {@link ReferenceContent} * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Lukáš Hornych, FG Forrest a.s. (c) 2022 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/EntityGroupFetch.java b/evita_query/src/main/java/io/evitadb/api/query/require/EntityGroupFetch.java index 79a58db6e..33e720961 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/EntityGroupFetch.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/EntityGroupFetch.java @@ -72,7 +72,7 @@ * - {@link HierarchyContent} * - {@link ReferenceContent} * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Lukáš Hornych, FG Forrest a.s. (c) 2022 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsConjunction.java b/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsConjunction.java index c98a30c48..1bd93deb8 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsConjunction.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsConjunction.java @@ -95,7 +95,7 @@ * facet red(12). If require `facetGroupsConjunction('parameterType', 1)` is passed in the query filtering condition will * be composed as: blue(11) AND red(12) * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsDisjunction.java b/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsDisjunction.java index 27fed7001..1dd6dc69a 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsDisjunction.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsDisjunction.java @@ -95,7 +95,7 @@ * have facet blue as well as facet large and action products tag (AND). If require `facetGroupsDisjunction('tag', 3)` * is passed in the query, filtering condition will be composed as: (`blue(11)` AND `large(22)`) OR `new products(31)` * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsNegation.java b/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsNegation.java index 1179783ef..b630d29ca 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsNegation.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/FacetGroupsNegation.java @@ -76,7 +76,7 @@ * Selecting any option in the RAM facet group predicts returning thousands of results, while the ROM facet group with * default behavior predicts only a dozen of them. * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/FacetSummary.java b/evita_query/src/main/java/io/evitadb/api/query/require/FacetSummary.java index 86f089ae7..a61a2ba2e 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/FacetSummary.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/FacetSummary.java @@ -123,7 +123,7 @@ * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/FacetSummaryOfReference.java b/evita_query/src/main/java/io/evitadb/api/query/require/FacetSummaryOfReference.java index 4152278d7..4eac7e732 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/FacetSummaryOfReference.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/FacetSummaryOfReference.java @@ -118,7 +118,7 @@ * The ordering constraints can only target properties on the target entity and cannot target reference attributes in * the source entity that are specific to a relationship with the target entity. * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyChildren.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyChildren.java index 9dc6dc067..6657e2fcd 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyChildren.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyChildren.java @@ -104,7 +104,7 @@ * respect the rules that apply to the hierarchyWithin so that the calculated number remains consistent for the end * user. * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2023 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyContent.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyContent.java index 89df421ae..17c52afcb 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyContent.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyContent.java @@ -66,7 +66,7 @@ * ) * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyDistance.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyDistance.java index 31005f15c..d0d9a0e48 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyDistance.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyDistance.java @@ -73,7 +73,7 @@ * also returns a computed subcategories data structure that lists the flat category list the currently focused category * Audio. * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2023 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyFromNode.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyFromNode.java index ad436edc8..e4c8a21f1 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyFromNode.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyFromNode.java @@ -125,7 +125,7 @@ * the calculated statistics to respect the rules that apply to the hierarchyWithin so that the calculated number * remains consistent for the end user. * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2023 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyFromRoot.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyFromRoot.java index 65b2b781b..c48acc7aa 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyFromRoot.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyFromRoot.java @@ -107,7 +107,7 @@ * the calculated statistics to respect the rules that apply to the {@link HierarchyWithin} so that the calculated * number remains consistent for the end user. * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2023 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyLevel.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyLevel.java index 94c45d778..df6528247 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyLevel.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyLevel.java @@ -66,7 +66,7 @@ * The query lists products in Audio category and its subcategories. Along with the products returned, it * also returns a computed megaMenu data structure that lists top two levels of the entire hierarchy. * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2023 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyNode.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyNode.java index a9a3220c5..7626d7641 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyNode.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyNode.java @@ -78,7 +78,7 @@ * ) * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2023 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyOfReference.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyOfReference.java index ecb7c8294..20792da37 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyOfReference.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyOfReference.java @@ -80,7 +80,7 @@ * - {@link HierarchyChildren} * - {@link HierarchyParents} * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyOfSelf.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyOfSelf.java index c48a468d0..bce7361e1 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyOfSelf.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyOfSelf.java @@ -71,7 +71,7 @@ * - {@link HierarchyChildren} * - {@link HierarchyParents} * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyParents.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyParents.java index 6bd5efc7f..eee2690a1 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyParents.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyParents.java @@ -101,7 +101,7 @@ * simple: when you render a menu for the query result, you want the calculated statistics to respect the rules that * apply to the {@link HierarchyWithin} so that the calculated number remains consistent for the end user. * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2023 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchySiblings.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchySiblings.java index cde44c8fd..6c5cbd89d 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchySiblings.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchySiblings.java @@ -111,7 +111,7 @@ * already defined on the enclosing parents constraint, and the siblings constraint simply extends the data available * in its data structure. * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2023 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyStatistics.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyStatistics.java index 66dadbfa1..9b7326c41 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyStatistics.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyStatistics.java @@ -79,7 +79,7 @@ * even considering that all the indexes are in-memory. Caching is probably the only way out if you really need * to crunch these numbers. * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2023 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyStopAt.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyStopAt.java index a89989747..6f080a8e0 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyStopAt.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyStopAt.java @@ -50,7 +50,7 @@ * which define the constraint that stops traversing the hierarchy tree when it's satisfied by a currently traversed * node. * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2023 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/Page.java b/evita_query/src/main/java/io/evitadb/api/query/require/Page.java index 269471870..6cb2b2fe8 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/Page.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/Page.java @@ -54,7 +54,7 @@ * page(1, 24) * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/PriceContent.java b/evita_query/src/main/java/io/evitadb/api/query/require/PriceContent.java index 7524cc2d2..4840850c0 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/PriceContent.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/PriceContent.java @@ -65,7 +65,7 @@ * priceContentAll() * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/PriceHistogram.java b/evita_query/src/main/java/io/evitadb/api/query/require/PriceHistogram.java index 777209c1d..c96922406 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/PriceHistogram.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/PriceHistogram.java @@ -49,7 +49,7 @@ * priceHistogram(20) * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/PriceType.java b/evita_query/src/main/java/io/evitadb/api/query/require/PriceType.java index 59a2114cb..ba389dd42 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/PriceType.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/PriceType.java @@ -50,7 +50,7 @@ * useOfPrice(WITH_TAX) * ``` * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/QueryTelemetry.java b/evita_query/src/main/java/io/evitadb/api/query/require/QueryTelemetry.java index 7917dff69..7a925608a 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/QueryTelemetry.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/QueryTelemetry.java @@ -42,7 +42,7 @@ * queryTelemetry() * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/ReferenceContent.java b/evita_query/src/main/java/io/evitadb/api/query/require/ReferenceContent.java index d03f1d9c3..c413671a0 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/ReferenceContent.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/ReferenceContent.java @@ -149,7 +149,7 @@ * ) * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/Require.java b/evita_query/src/main/java/io/evitadb/api/query/require/Require.java index c23488103..174fc1623 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/Require.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/Require.java @@ -50,7 +50,7 @@ * ) * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný, FG Forrest a.s. (c) 2021 */ diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/Strip.java b/evita_query/src/main/java/io/evitadb/api/query/require/Strip.java index b64ed953d..6aeed59da 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/Strip.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/Strip.java @@ -52,7 +52,7 @@ * strip(52, 24) * * - * Visit detailed user documentation + *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2021 */ From 0e6adc7837106613fd718422e78c478f22be1101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Novotn=C3=BD?= Date: Fri, 8 Dec 2023 16:19:02 +0100 Subject: [PATCH 14/52] docs: updated documentation --- .../api/example/associated-data-class.java | 49 +++--- .../example/associated-data-interface.java | 6 +- .../api/example/associated-data-record.java | 11 +- .../en/use/api/example/attribute-class.java | 23 +-- .../use/api/example/attribute-interface.java | 6 +- .../en/use/api/example/attribute-record.java | 12 +- .../en/use/api/example/fetching-example.cs | 44 +++--- .../use/api/example/fetching-example.graphql | 46 ++++++ .../en/use/api/example/fetching-example.rest | 28 ++++ .../user/en/use/api/example/parent-class.java | 25 ++- .../user/en/use/api/example/price-class.java | 35 +++-- .../en/use/api/example/price-interface.java | 10 +- .../en/use/api/example/primary-key-class.java | 16 ++ .../api/example/primary-key-interface.java | 17 +- .../en/use/api/example/reference-class.java | 146 ++++++++++++------ .../use/api/example/reference-interface.java | 120 +++++++------- .../en/use/api/example/reference-record.java | 66 ++++---- .../documentation/UserDocumentationTest.java | 108 ++++++++++++- .../META-INF/documentation/imports.java | 17 +- 19 files changed, 529 insertions(+), 256 deletions(-) create mode 100644 documentation/user/en/use/api/example/fetching-example.graphql create mode 100644 documentation/user/en/use/api/example/fetching-example.rest diff --git a/documentation/user/en/use/api/example/associated-data-class.java b/documentation/user/en/use/api/example/associated-data-class.java index edf64d775..b7f776013 100644 --- a/documentation/user/en/use/api/example/associated-data-class.java +++ b/documentation/user/en/use/api/example/associated-data-class.java @@ -2,44 +2,51 @@ @Data public class MyEntity { - // contains associated data `warrantySpecification` if fetched and not null - @AssociatedData(warrantySpecification = "warrantySpecification", locale = true) - @Nullable private final String warrantySpecification, + // example ComplexDataObject type + public record Localization( + @Nonnull Map texts + ) { } // contains associated data `warrantySpecification` if fetched and not null - @AssociatedDataRef("warrantySpecification") - @Nullable private final String warrantySpecificationAgain, + @AssociatedData(name = "warrantySpecification", localized = true) + @Nullable private final String warrantySpecification; - // contains associated data `warrantySpecification` or empty optional wrapper if not set or fetched + // contains associated data `warrantySpecification` if fetched and not null @AssociatedDataRef("warrantySpecification") - @Nonnull private final Optional warrantySpecificationIfPresent, + @Nullable private final String warrantySpecificationAgain; // contains associated data `parameters` or null if not fetched or not set @AssociatedDataRef("parameters") - @Nullable private final String[] parameters, + @Nullable private final String[] parameters; // contains associated data `parameters` or empty collection if not fetched or not set (it never contains null value) @AssociatedDataRef("parameters") - @Nonnull private final Collection parametersAsCollection, + @Nonnull private final Collection parametersAsCollection; // contains associated data `parameters` or empty list if not fetched or not set (it never contains null value) @AssociatedDataRef("parameters") - @Nonnull private final List parametersAsList, + @Nonnull private final List parametersAsList; // contains associated data `parameters` or empty set if not fetched or not set (it never contains null value) @AssociatedDataRef("parameters") - @Nonnull private final Set parametersAsSet, + @Nonnull private final Set parametersAsSet; // contains associated data `localization` or null if not fetched or not set @AssociatedDataRef("localization") - @Nullable private final Localization localization, - - // contains associated data `localization` or empty optional if not fetched or not set - @AssociatedDataRef("localization") - @Nullable private final Optional localizationIfPresent - - public record Localization( - @Nonnull Map texts - ) { } - + @Nullable private final Localization localization; + + // constructor is usually generated by Lombok + public MyEntity( + String warrantySpecification, String warrantySpecificationAgain, + String[] parameters, Collection parametersAsCollection, List parametersAsList, Set parametersAsSet, + Localization localization + ) { + this.warrantySpecification = warrantySpecification; + this.warrantySpecificationAgain = warrantySpecificationAgain; + this.parameters = parameters; + this.parametersAsCollection = parametersAsCollection; + this.parametersAsList = parametersAsList; + this.parametersAsSet = parametersAsSet; + this.localization = localization; + } } \ No newline at end of file diff --git a/documentation/user/en/use/api/example/associated-data-interface.java b/documentation/user/en/use/api/example/associated-data-interface.java index 4df9c95f0..77ed0c4aa 100644 --- a/documentation/user/en/use/api/example/associated-data-interface.java +++ b/documentation/user/en/use/api/example/associated-data-interface.java @@ -2,7 +2,7 @@ public interface MyEntity { // returns associated data `warrantySpecification` if fetched and not null - @AssociatedData(warrantySpecification = "warrantySpecification", locale = true) + @AssociatedData(name = "warrantySpecification", localized = true) @Nullable String getWarrantySpecification(); // returns associated data `warrantySpecification` in requested locale @@ -20,7 +20,7 @@ public interface MyEntity { // returns associated data `warrantySpecification` or empty optional wrapper, if not fetched (unknown state) throws exception @AssociatedDataRef("warrantySpecification") - @Nonnull Optional getWarrantySpecificationOrThrow() throws ContextMissingException; + @Nonnull Optional getWarrantySpecificationIfPresent() throws ContextMissingException; // returns associated data `parameters` or null if not fetched or not set @AssociatedDataRef("parameters") @@ -46,7 +46,7 @@ public interface MyEntity { // returns associated data `parameters` as collection (or you can use list/set/array variants) // or empty optional if associated data is not set, throws exception when associated data is not fetched @AssociatedDataRef("parameters") - @Nonnull Optional> getParametersAsCollection() throws ContextMissingException; + @Nonnull Optional> getParametersAsCollectionIfPresent() throws ContextMissingException; // returns associated data `localization` or null if not fetched or not set @AssociatedDataRef("localization") diff --git a/documentation/user/en/use/api/example/associated-data-record.java b/documentation/user/en/use/api/example/associated-data-record.java index d195818a8..9021dfb2d 100644 --- a/documentation/user/en/use/api/example/associated-data-record.java +++ b/documentation/user/en/use/api/example/associated-data-record.java @@ -1,17 +1,13 @@ @EntityRef("Product") public record MyEntity( // contains associated data `warrantySpecification` if fetched and not null - @AssociatedData(warrantySpecification = "warrantySpecification", locale = true) + @AssociatedData(name = "warrantySpecification", localized = true) @Nullable String warrantySpecification, // contains associated data `warrantySpecification` if fetched and not null @AssociatedDataRef("warrantySpecification") @Nullable String warrantySpecificationAgain, - // contains associated data `warrantySpecification` or empty optional wrapper if not set or fetched - @AssociatedDataRef("warrantySpecification") - @Nonnull Optional warrantySpecificationIfPresent, - // contains associated data `parameters` or null if not fetched or not set @AssociatedDataRef("parameters") @Nullable String[] parameters, @@ -30,11 +26,8 @@ public record MyEntity( // contains associated data `localization` or null if not fetched or not set @AssociatedDataRef("localization") - @Nullable Localization localization, + @Nullable Localization localization - // contains associated data `localization` or empty optional if not fetched or not set - @AssociatedDataRef("localization") - @Nullable Optional localizationIfPresent ) { public record Localization( diff --git a/documentation/user/en/use/api/example/attribute-class.java b/documentation/user/en/use/api/example/attribute-class.java index a24cb916d..e0db54e70 100644 --- a/documentation/user/en/use/api/example/attribute-class.java +++ b/documentation/user/en/use/api/example/attribute-class.java @@ -3,17 +3,13 @@ public class MyEntity { // contains attribute `name` if fetched and not null - @Attribute(name = "name", locale = true) + @Attribute(name = "name", localized = true) @Nullable private final String name; // contains attribute `name` if fetched and not null @AttributeRef("name") @Nullable private final String nameAgain; - // contains attribute `name` or empty optional wrapper - @AttributeRef("name") - @Nonnull private final Optional name; - // contains attribute `markets` or null if not fetched or not set @AttributeRef("markets") @Nullable private final String[] markets; @@ -30,9 +26,16 @@ public class MyEntity { @AttributeRef("markets") @Nonnull private final Set marketsAsSet; - // contains attribute `markets` as collection (or you can use list/set/array variants) - // or empty optional if attribute is not set or fetched - @AttributeRef("markets") - @Nonnull private final Optional> marketsAsCollection; - + // constructor is usually generated by Lombok + public MyEntity( + String name, String nameAgain, + String[] markets, Collection marketsAsCollection, List marketsAsList, Set marketsAsSet + ) { + this.name = name; + this.nameAgain = nameAgain; + this.markets = markets; + this.marketsAsCollection = marketsAsCollection; + this.marketsAsList = marketsAsList; + this.marketsAsSet = marketsAsSet; + } } \ No newline at end of file diff --git a/documentation/user/en/use/api/example/attribute-interface.java b/documentation/user/en/use/api/example/attribute-interface.java index 9b16e8961..2d4a26ed6 100644 --- a/documentation/user/en/use/api/example/attribute-interface.java +++ b/documentation/user/en/use/api/example/attribute-interface.java @@ -2,7 +2,7 @@ public interface MyEntity { // returns attribute `name` if fetched and not null - @Attribute(name = "name", locale = true) + @Attribute(name = "name", localized = true) @Nullable String getName(); // returns attribute `name` in requested locale @@ -20,7 +20,7 @@ public interface MyEntity { // returns attribute `name` or empty optional wrapper, if not fetched (unknown state) throws exception @AttributeRef("name") - @Nonnull Optional getNameOrThrow() throws ContextMissingException; + @Nonnull Optional getNameIfPresent() throws ContextMissingException; // returns attribute `markets` or null if not fetched or not set @AttributeRef("markets") @@ -46,6 +46,6 @@ public interface MyEntity { // returns attribute `markets` as collection (or you can use list/set/array variants) // or empty optional if attribute is not set, throws exception when attribute is not fetched @AttributeRef("markets") - @Nonnull Optional> getMarketsAsCollection() throws ContextMissingException; + @Nonnull Optional> getMarketsAsCollectionIfPresent() throws ContextMissingException; } \ No newline at end of file diff --git a/documentation/user/en/use/api/example/attribute-record.java b/documentation/user/en/use/api/example/attribute-record.java index 159f1991f..136948952 100644 --- a/documentation/user/en/use/api/example/attribute-record.java +++ b/documentation/user/en/use/api/example/attribute-record.java @@ -1,17 +1,13 @@ @EntityRef("Product") public record MyEntity( // contains attribute `name` if fetched and not null - @Attribute(name = "name", locale = true) + @Attribute(name = "name", localized = true) @Nullable String name, // contains attribute `name` if fetched and not null @AttributeRef("name") @Nullable String nameAgain, - // contains attribute `name` or empty optional wrapper - @AttributeRef("name") - @Nonnull Optional name, - // contains attribute `markets` or null if not fetched or not set @AttributeRef("markets") @Nullable String[] markets, @@ -26,12 +22,8 @@ public record MyEntity( // contains attribute `markets` or empty set if not fetched or not set (it never contains null value) @AttributeRef("markets") - @Nonnull Set marketsAsSet, + @Nonnull Set marketsAsSet - // contains attribute `markets` as collection (or you can use list/set/array variants) - // or empty optional if attribute is not set or fetched - @AttributeRef("markets") - @Nonnull Optional> marketsAsCollection ) { } \ No newline at end of file diff --git a/documentation/user/en/use/api/example/fetching-example.cs b/documentation/user/en/use/api/example/fetching-example.cs index eae8c5544..6a27b1836 100644 --- a/documentation/user/en/use/api/example/fetching-example.cs +++ b/documentation/user/en/use/api/example/fetching-example.cs @@ -1,26 +1,24 @@ EvitaResponse entities = evita.QueryCatalog( "evita", - session => { - return session.QuerySealedEntity( - Query( - Collection("Product"), - FilterBy( - And( - EntityPrimaryKeyInSet(1), - EntityLocaleEquals(new CultureInfo("en")), - PriceInPriceLists("basic"), - PriceInCurrency(new Currency("CZK")) - ) - ), - Require( - EntityFetch( - AttributeContent("name"), - AssociatedDataContentAll(), - PriceContentRespectingFilter(), - ReferenceContent("brand") - ) - ) - ) - ); - } + session => session.QuerySealedEntity( + Query( + Collection("Product"), + FilterBy( + And( + EntityPrimaryKeyInSet(1), + EntityLocaleEquals(CultureInfo.GetCultureInfo("en")), + PriceInPriceLists("basic"), + PriceInCurrency(new Currency("CZK")) + ) + ), + Require( + EntityFetch( + AttributeContent("name"), + AssociatedDataContentAll(), + PriceContentRespectingFilter(), + ReferenceContent("brand") + ) + ) + ) + ) ); \ No newline at end of file diff --git a/documentation/user/en/use/api/example/fetching-example.graphql b/documentation/user/en/use/api/example/fetching-example.graphql new file mode 100644 index 000000000..803626b1a --- /dev/null +++ b/documentation/user/en/use/api/example/fetching-example.graphql @@ -0,0 +1,46 @@ +{ + queryProduct( + filterBy: { + and: [ + { + entityPrimaryKeyInSet: [ + 1 + ], + entityLocaleEquals: en, + priceInPriceLists: [ + "basic" + ], + priceInCurrency: CZK + } + ] + } + ) { + recordPage { + data { + primaryKey + attributes { + name + } + associatedData { + localization + allActiveUrls + allRelatedFiles + } + priceForSale { + priceWithoutTax( +formatted: true +withCurrency: true + ) + priceWithTax( +formatted: true +withCurrency: true + ) + taxRate + } + brand { + referencedPrimaryKey + } + } + } + } +} \ No newline at end of file diff --git a/documentation/user/en/use/api/example/fetching-example.rest b/documentation/user/en/use/api/example/fetching-example.rest new file mode 100644 index 000000000..152befbe9 --- /dev/null +++ b/documentation/user/en/use/api/example/fetching-example.rest @@ -0,0 +1,28 @@ +POST /rest/evita/Product/query + +{ + "filterBy" : { + "and" : [ + { + "entityPrimaryKeyInSet" : [ + 1 + ], + "entityLocaleEquals" : "en", + "priceInPriceLists" : [ + "basic" + ], + "priceInCurrency" : "CZK" + } + ] + }, + "require" : { + "entityFetch" : { + "attributeContent" : [ + "name" + ], + "associatedDataContentAll" : true, + "priceContentRespectingFilter" : [ ], + "referenceBrandContent" : { } + } + } +} \ No newline at end of file diff --git a/documentation/user/en/use/api/example/parent-class.java b/documentation/user/en/use/api/example/parent-class.java index 1f982ec90..6da32caed 100644 --- a/documentation/user/en/use/api/example/parent-class.java +++ b/documentation/user/en/use/api/example/parent-class.java @@ -2,20 +2,29 @@ @Data public class MyEntity { - // contains id of parent entity, or null if this entity is a root entity + // contains id of parent entity; or null if this entity is a root entity @ParentEntity - @Nullable private final Integer parentId, + @Nullable private final Integer parentId; - // contains parent entity, or null if this entity is a root entity + // contains parent entity; or null if this entity is a root entity @ParentEntity - @Nullable private final SealedEntity parentEntity, + @Nullable private final SealedEntity parentEntity; - // contains reference to a parent entity, or null if this entity is a root entity + // contains reference to a parent entity; or null if this entity is a root entity @ParentEntity - @Nullable private final EntityReference parentEntityReference, + @Nullable private final EntityReference parentEntityReference; - // contains reference to a parent wrapped in this interface, or null if this entity is a root entity + // contains reference to a parent wrapped in this interface; or null if this entity is a root entity @ParentEntity - @Nullable private final MyEntity parentMyEntity + @Nullable private final MyEntity parentMyEntity; + // constructor is usually generated by Lombok + public MyEntity( + Integer parentId, SealedEntity parentEntity, EntityReference parentEntityReference, MyEntity parentMyEntity + ) { + this.parentId = parentId; + this.parentEntity = parentEntity; + this.parentEntityReference = parentEntityReference; + this.parentMyEntity = parentMyEntity; + } } \ No newline at end of file diff --git a/documentation/user/en/use/api/example/price-class.java b/documentation/user/en/use/api/example/price-class.java index dfb88a271..73e2343ab 100644 --- a/documentation/user/en/use/api/example/price-class.java +++ b/documentation/user/en/use/api/example/price-class.java @@ -1,33 +1,50 @@ @EntityRef("Product") @Data -public record MyEntity( +public class MyEntity { + // contains the prices calculated as selling price for particular query @PriceForSale - @Nullable private final PriceContract priceForSale, + @Nullable private final PriceContract priceForSale; // contains all the prices that compete for being the selling price for particular entity // from all those prices only the first one is used as selling price @PriceForSaleRef - @Nullable private final PriceContract[] allPricesAvailableForSale, + @Nullable private final PriceContract[] allPricesAvailableForSale; // contains price from the price list with name `basic` if it was fetched from the server @Price(priceList = "basic") - @Nullable private final PriceContract basicPrice, + @Nullable private final PriceContract basicPrice; // contains all prices of the entity that were fetched from the server @Price - @NonNull private final Collection allPrices, + @Nonnull private final Collection allPrices; // contains all prices of the entity that were fetched from the server as list @Price - @NonNull private final List allPricesAsList, + @Nonnull private final List allPricesAsList; // contains all prices of the entity that were fetched from the server as set @Price - @NonNull private final Set allPricesAsSet, + @Nonnull private final Set allPricesAsSet; // contains all prices of the entity that were fetched from the server as array @Price - @Nullable private final PriceContract[] allPricesAsArray -) { + @Nullable private final PriceContract[] allPricesAsArray; + + // constructor is usually generated by Lombok + public MyEntity( + PriceContract priceForSale, + PriceContract[] allPricesAvailableForSale, + PriceContract basicPrice, + Collection allPrices, List allPricesAsList, + Set allPricesAsSet, PriceContract[] allPricesAsArray + ) { + this.priceForSale = priceForSale; + this.allPricesAvailableForSale = allPricesAvailableForSale; + this.basicPrice = basicPrice; + this.allPrices = allPrices; + this.allPricesAsList = allPricesAsList; + this.allPricesAsSet = allPricesAsSet; + this.allPricesAsArray = allPricesAsArray; + } } \ No newline at end of file diff --git a/documentation/user/en/use/api/example/price-interface.java b/documentation/user/en/use/api/example/price-interface.java index e0f6c32b0..2519683e4 100644 --- a/documentation/user/en/use/api/example/price-interface.java +++ b/documentation/user/en/use/api/example/price-interface.java @@ -9,7 +9,7 @@ public interface MyEntity { // returns the prices calculated as selling price for particular query, // or empty if the price information was not fetched from the server or not requested in the query @PriceForSale - @NonNull Optional getPriceForSaleIfPresent(); + @Nonnull Optional getPriceForSaleIfPresent(); // returns all the prices that compete for being the selling price for particular entity // from all those prices only the first one is used as selling price @@ -59,22 +59,22 @@ public interface MyEntity { // returns price from the price list with name `basic` if it was fetched from the server, or empty value @Price(priceList = "basic") - @NonNull Optional getBasicPriceIfPresent(); + @Nonnull Optional getBasicPriceIfPresent(); // returns all prices of the entity that were fetched from the server // throws ContextMissingException if the price information was not fetched from the server @Price - @NonNull Collection getAllPrices() throws ContextMissingException; + @Nonnull Collection getAllPrices() throws ContextMissingException; // returns all prices of the entity that were fetched from the server as list // throws ContextMissingException if the price information was not fetched from the server @Price - @NonNull List getAllPricesAsList() throws ContextMissingException; + @Nonnull List getAllPricesAsList() throws ContextMissingException; // returns all prices of the entity that were fetched from the server as set // throws ContextMissingException if the price information was not fetched from the server @Price - @NonNull Set getAllPricesAsSet() throws ContextMissingException; + @Nonnull Set getAllPricesAsSet() throws ContextMissingException; // returns all prices of the entity that were fetched from the server as array // throws ContextMissingException if the price information was not fetched from the server diff --git a/documentation/user/en/use/api/example/primary-key-class.java b/documentation/user/en/use/api/example/primary-key-class.java index b2281c17d..b8ac5bd1d 100644 --- a/documentation/user/en/use/api/example/primary-key-class.java +++ b/documentation/user/en/use/api/example/primary-key-class.java @@ -10,4 +10,20 @@ public class MyEntity { @PrimaryKeyRef private final Integer idRefAsIntegerObject; @PrimaryKeyRef private final long idRefAsLong; @PrimaryKeyRef private final Long idRefAsLongObject; + + // constructor is usually generated by Lombok + public MyEntity( + int id, Integer idAsIntegerObject, long idAsLong, Long idAsLongObject, + int idRef, Integer idRefAsIntegerObject, long idRefAsLong, Long idRefAsLongObject + ) { + this.id = id; + this.idAsIntegerObject = idAsIntegerObject; + this.idAsLong = idAsLong; + this.idAsLongObject = idAsLongObject; + this.idRef = idRef; + this.idRefAsIntegerObject = idRefAsIntegerObject; + this.idRefAsLong = idRefAsLong; + this.idRefAsLongObject = idRefAsLongObject; + } + } \ No newline at end of file diff --git a/documentation/user/en/use/api/example/primary-key-interface.java b/documentation/user/en/use/api/example/primary-key-interface.java index f7d5e3378..70907b392 100644 --- a/documentation/user/en/use/api/example/primary-key-interface.java +++ b/documentation/user/en/use/api/example/primary-key-interface.java @@ -1,14 +1,13 @@ @EntityRef("Product") public interface MyEntity { - @PrimaryKey int any(); - @PrimaryKey Integer any(); - @PrimaryKey long any(); - @PrimaryKey Long any(); - - @PrimaryKeyRef int any(); - @PrimaryKeyRef Integer any(); - @PrimaryKeyRef long any(); - @PrimaryKeyRef Long any(); + @PrimaryKey int id(); + @PrimaryKey Integer idAsIntegerObject(); + @PrimaryKey long idAsLong(); + @PrimaryKey Long idAsLongObject(); + @PrimaryKeyRef int idAlternative(); + @PrimaryKeyRef Integer idAlternativeAsIntegerObject(); + @PrimaryKeyRef long idAlternativeAsLong(); + @PrimaryKeyRef Long idAlternativeAsLongObject(); } \ No newline at end of file diff --git a/documentation/user/en/use/api/example/reference-class.java b/documentation/user/en/use/api/example/reference-class.java index 8a78bb22f..a40544075 100644 --- a/documentation/user/en/use/api/example/reference-class.java +++ b/documentation/user/en/use/api/example/reference-class.java @@ -6,49 +6,110 @@ public class MyEntity { // and the Brand entity is fetched along with MyEntity // throws ContextMissingException if the reference information was not fetched from the server @ReferenceRef("brand") - @Nullable private final Brand brand, + @Nullable private final Brand brand; // component contains referenced Brand entity primary key if such reference with cardinality ZERO_OR_ONE exists // throws ContextMissingException if the reference information was not fetched from the server @ReferenceRef("brand") - @Nullable private final Integer brandId, + @Nullable private final Integer brandId; // component contains collection of referenced ProductParameter references or empty list // reference `parameters` has cardinality ZERO_OR_MORE // throws ContextMissingException if the reference information was not fetched from the server @ReferenceRef("parameters") - @NonNull private final List parameters, + @NonNull private final List parameters; // alternative format for `Parameters` method with similar behaviour @ReferenceRef("parameters") - @NonNull private final Set parametersAsSet, + @NonNull private final Set parametersAsSet; // alternative format for `Parameters` method with similar behaviour @ReferenceRef("parameters") - @NonNull private final Collection parameterAsCollection, + @NonNull private final Collection parameterAsCollection; // alternative format for `Parameters` method with similar behaviour @ReferenceRef("parameters") - @NonNull private final ProductParameter[] parameterAsArray, + @NonNull private final ProductParameter[] parameterAsArray; // component contains array of referenced Parameter entities or null // reference `parameters` has cardinality ZERO_OR_MORE // throws ContextMissingException if the reference information was not fetched from the server @ReferenceRef("parameters") - @Nullable private final int[] parameterIds - + @Nullable private final int[] parameterIds; + + // constructor is usually generated by Lombok + public MyEntity( + Brand brand, Integer brandId, + List parameters, Set parametersAsSet, + Collection parameterAsCollection, ProductParameter[] parameterAsArray, + int[] parameterIds + ) { + this.brand = brand; + this.brandId = brandId; + this.parameters = parameters; + this.parametersAsSet = parametersAsSet; + this.parameterAsCollection = parameterAsCollection; + this.parameterAsArray = parameterAsArray; + this.parameterIds = parameterIds; + } // simplified Brand entity interface // this example demonstrates the option to return directly referenced entities from the main entity @EntityRef("Brand") @Data - public class Brand extends Serializable { + public class Brand implements Serializable { @PrimaryKeyRef - private final int id, + private final int id; // attribute code of the Brand entity @AttributeRef("code") - @Nullable private final String code + @Nullable private final String code; + + // constructor is usually generated by Lombok + public Brand(int id, String code) { + this.id = id; + this.code = code; + } + + } + + // simplified Parameter entity interface + @EntityRef("Parameter") + @Data + public class Parameter implements Serializable { + + @PrimaryKeyRef + private final int id; + + // attribute code of the Parameter entity + @AttributeRef("code") + @Nullable private final String code; + + // constructor is usually generated by Lombok + public Parameter(int id, String code) { + this.id = id; + this.code = code; + } + + } + + // simplified ParameterGroup entity interface + @EntityRef("ParameterGroup") + @Data + public class ParameterGroup implements Serializable { + + @PrimaryKeyRef + private final int id; + + // attribute code of the ParameterGroup entity + @AttributeRef("code") + @Nullable private final String code; + + // constructor is usually generated by Lombok + public ParameterGroup(int id, String code) { + this.id = id; + this.code = code; + } } @@ -57,69 +118,56 @@ public class Brand extends Serializable { // to the referenced entities (both grouping and referenced entities) @EntityRef("Parameter") @Data - public class ProductParameter extends Serializable { + public class ProductParameter implements Serializable { @ReferencedEntity - private final int primaryKey, + private final int primaryKey; // attribute code of the reference to the Parameter entity @AttributeRef("priority") - @Nullable private final Long priority, + @Nullable private final Long priority; // primary key of the referenced entity of the reference @ReferencedEntity - @Nullable private final Integer parameter, + @Nullable private final Integer parameter; // reference to the referenced entity descriptor of the reference @ReferencedEntity - @Nullable private final EntityReferenceContract parameterEntityClassifier, + @Nullable private final EntityReferenceContract parameterEntityClassifier; // reference to the referenced entity of the reference // throws ContextMissingException if the referenced entity was not fetched from the server @ReferencedEntity - @Nullable private final Parameter parameterEntity, + @Nullable private final Parameter parameterEntity; // primary key of the grouping entity of the reference to the Parameter entity @ReferencedEntityGroup - @Nullable private final Integer parameterGroup, + @Nullable private final Integer parameterGroup; // reference to the grouping entity descriptor of the reference to the Parameter entity @ReferencedEntityGroup - @Nullable private final EntityReferenceContract parameterGroupEntityClassifier, + @Nullable private final EntityReferenceContract parameterGroupEntityClassifier; // reference to the grouping entity of the reference to the Parameter entity // throws ContextMissingException if the referenced entity was not fetched from the server @ReferencedEntityGroup - @Nullable private final ParameterGroup parameterGroupEntity - - } - - // simplified Parameter entity interface - @EntityRef("Parameter") - @Data - public class ParameterGroupInterfaceEditor extends Serializable { - - @PrimaryKeyRef - int id, - - // attribute code of the Parameter entity - @AttributeRef("code") - @Nullable private final String code - - } - - // simplified ParameterGroup entity interface - @EntityRef("ParameterGroup") - @Data - public class ParameterGroupInterfaceEditor extends Serializable { - - @PrimaryKeyRef - int id, - - // attribute code of the ParameterGroup entity - @AttributeRef("code") - @Nullable private final String code - + @Nullable private final ParameterGroup parameterGroupEntity; + + // constructor is usually generated by Lombok + public ProductParameter( + int primaryKey, Long priority, + Integer parameter, EntityReferenceContract parameterEntityClassifier, Parameter parameterEntity, + Integer parameterGroup, EntityReferenceContract parameterGroupEntityClassifier, ParameterGroup parameterGroupEntity + ) { + this.primaryKey = primaryKey; + this.priority = priority; + this.parameter = parameter; + this.parameterEntityClassifier = parameterEntityClassifier; + this.parameterEntity = parameterEntity; + this.parameterGroup = parameterGroup; + this.parameterGroupEntityClassifier = parameterGroupEntityClassifier; + this.parameterGroupEntity = parameterGroupEntity; + } } } \ No newline at end of file diff --git a/documentation/user/en/use/api/example/reference-interface.java b/documentation/user/en/use/api/example/reference-interface.java index 2f0206dac..ac8db3d9d 100644 --- a/documentation/user/en/use/api/example/reference-interface.java +++ b/documentation/user/en/use/api/example/reference-interface.java @@ -1,60 +1,41 @@ @EntityRef("Product") public interface MyEntity { - // method returns referenced entity Brand if such reference with cardinality ZERO_OR_ONE exists - // and the Brand entity is fetched along with MyEntity - // throws ContextMissingException if the reference information was not fetched from the server - @ReferenceRef("brand") - @Nullable Brand getBrand() throws ContextMissingException; - - // method returns referenced Brand entity primary key if such reference with cardinality ZERO_OR_ONE exists - // throws ContextMissingException if the reference information was not fetched from the server - @ReferenceRef("brand") - @Nullable Integer getBrandId() throws ContextMissingException; + // simplified Brand entity interface + // this example demonstrates the option to return directly referenced entities from the main entity + @EntityRef("Brand") + public interface Brand extends Serializable { - // method returns collection of referenced ProductParameter references or empty list - // reference `parameters` has cardinality ZERO_OR_MORE - // throws ContextMissingException if the reference information was not fetched from the server - @ReferenceRef("parameters") - @NonNull List getParameters() throws ContextMissingException; + @PrimaryKeyRef + int getId(); - // alternative format for `getParameters` method with similar behaviour - @ReferenceRef("parameters") - @NonNull Set getParametersAsSet() throws ContextMissingException; + // attribute code of the Brand entity + @AttributeRef("code") + @Nullable String getCode(); - // alternative format for `getParameters` method with similar behaviour - @ReferenceRef("parameters") - @NonNull Collection getParameterAsCollection() throws ContextMissingException; + } - // alternative format for `getParameters` method with similar behaviour - @ReferenceRef("parameters") - @NonNull ProductParameter[] getParameterAsArray() throws ContextMissingException; + // simplified Parameter entity interface + @EntityRef("Parameter") + public interface Parameter extends Serializable { - // method returns array of referenced Parameter entities or empty value if the reference information - // was not fetched from the server or there is no reference to Parameter from the current product - @ReferenceRef("parameters") - @NonNull Optional getParametersIfPresent(); + @PrimaryKeyRef + int getId(); - // method returns array of referenced Parameter entities or null - // reference `parameters` has cardinality ZERO_OR_MORE - // throws ContextMissingException if the reference information was not fetched from the server - @ReferenceRef("parameters") - @Nullable int[] getParameterIds() throws ContextMissingException; + // attribute code of the Parameter entity + @AttributeRef("code") + @Nullable String getCode(); - // method returns array of referenced Parameter entity primary keys or empty value if the reference information - // was not fetched from the server or there is no reference to Parameter from the current product - @ReferenceRef("parameters") - @NonNull Optional getParameterIdsIfPresent(); + } - // simplified Brand entity interface - // this example demonstrates the option to return directly referenced entities from the main entity - @EntityRef("Brand") - public interface Brand extends Serializable { + // simplified ParameterGroup entity interface + @EntityRef("ParameterGroup") + public interface ParameterGroup extends Serializable { @PrimaryKeyRef int getId(); - // attribute code of the Brand entity + // attribute code of the ParameterGroup entity @AttributeRef("code") @Nullable String getCode(); @@ -101,30 +82,49 @@ public interface ProductParameter extends Serializable { } - // simplified Parameter entity interface - @EntityRef("Parameter") - public interface ParameterGroupInterfaceEditor extends Serializable { + // method returns referenced entity Brand if such reference with cardinality ZERO_OR_ONE exists + // and the Brand entity is fetched along with MyEntity + // throws ContextMissingException if the reference information was not fetched from the server + @ReferenceRef("brand") + @Nullable Brand getBrand() throws ContextMissingException; - @PrimaryKeyRef - int getId(); + // method returns referenced Brand entity primary key if such reference with cardinality ZERO_OR_ONE exists + // throws ContextMissingException if the reference information was not fetched from the server + @ReferenceRef("brand") + @Nullable Integer getBrandId() throws ContextMissingException; - // attribute code of the Parameter entity - @AttributeRef("code") - @Nullable String getCode(); + // method returns collection of referenced ProductParameter references or empty list + // reference `parameters` has cardinality ZERO_OR_MORE + // throws ContextMissingException if the reference information was not fetched from the server + @ReferenceRef("parameters") + @Nonnull List getParameters() throws ContextMissingException; - } + // alternative format for `getParameters` method with similar behaviour + @ReferenceRef("parameters") + @Nonnull Set getParametersAsSet() throws ContextMissingException; - // simplified ParameterGroup entity interface - @EntityRef("ParameterGroup") - public interface ParameterGroupInterfaceEditor extends Serializable { + // alternative format for `getParameters` method with similar behaviour + @ReferenceRef("parameters") + @Nonnull Collection getParameterAsCollection() throws ContextMissingException; - @PrimaryKeyRef - int getId(); + // alternative format for `getParameters` method with similar behaviour + @ReferenceRef("parameters") + @Nonnull ProductParameter[] getParameterAsArray() throws ContextMissingException; - // attribute code of the ParameterGroup entity - @AttributeRef("code") - @Nullable String getCode(); + // method returns array of referenced Parameter entities or empty value if the reference information + // was not fetched from the server or there is no reference to Parameter from the current product + @ReferenceRef("parameters") + @Nonnull Optional getParametersIfPresent(); - } + // method returns array of referenced Parameter entities or null + // reference `parameters` has cardinality ZERO_OR_MORE + // throws ContextMissingException if the reference information was not fetched from the server + @ReferenceRef("parameters") + @Nullable int[] getParameterIds() throws ContextMissingException; + + // method returns array of referenced Parameter entity primary keys or empty value if the reference information + // was not fetched from the server or there is no reference to Parameter from the current product + @ReferenceRef("parameters") + @Nonnull Optional getParameterIdsIfPresent(); } \ No newline at end of file diff --git a/documentation/user/en/use/api/example/reference-record.java b/documentation/user/en/use/api/example/reference-record.java index 944aca646..99257860f 100644 --- a/documentation/user/en/use/api/example/reference-record.java +++ b/documentation/user/en/use/api/example/reference-record.java @@ -1,5 +1,5 @@ @EntityRef("Product") -public interface MyEntity( +public record MyEntity( // component contains referenced entity Brand if such reference with cardinality ZERO_OR_ONE exists // and the Brand entity is fetched along with MyEntity @@ -49,7 +49,37 @@ public record Brand( @AttributeRef("code") @Nullable String code - ) extends Serializable { + ) implements Serializable { + + } + + // simplified Parameter entity interface + @EntityRef("Parameter") + public record Parameter( + + @PrimaryKeyRef + int id, + + // attribute code of the Parameter entity + @AttributeRef("code") + @Nullable String code + + ) implements Serializable { + + } + + // simplified ParameterGroup entity interface + @EntityRef("ParameterGroup") + public record ParameterGroup( + + @PrimaryKeyRef + int id, + + // attribute code of the ParameterGroup entity + @AttributeRef("code") + @Nullable String code + + ) implements Serializable { } @@ -92,37 +122,7 @@ public record ProductParameter( @ReferencedEntityGroup @Nullable ParameterGroup parameterGroupEntity - ) extends Serializable { - - } - - // simplified Parameter entity interface - @EntityRef("Parameter") - public interface ParameterGroupInterfaceEditor( - - @PrimaryKeyRef - int id, - - // attribute code of the Parameter entity - @AttributeRef("code") - @Nullable String code - - ) extends Serializable { - - } - - // simplified ParameterGroup entity interface - @EntityRef("ParameterGroup") - public interface ParameterGroupInterfaceEditor( - - @PrimaryKeyRef - int id, - - // attribute code of the ParameterGroup entity - @AttributeRef("code") - @Nullable String code - - ) extends Serializable { + ) implements Serializable { } 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 64d0d09bc..c0cdd45ea 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 @@ -92,6 +92,14 @@ public class UserDocumentationTest implements EvitaTestSupport { "\\s*\\[.*?]\\((.*?)\\)\\s*", Pattern.DOTALL | Pattern.MULTILINE ); + /** + * + * Pattern for searching for blocks. + */ + private static final Pattern SOURCE_ALTERNATIVE_TABS_PATTERN = Pattern.compile( + "\\s*\\[.*?]\\((.*?)\\)\\s*", + Pattern.DOTALL | Pattern.MULTILINE + ); /** * Pattern for searching for blocks. */ @@ -308,6 +316,42 @@ private static List findRelatedFiles(@Nonnull Path theFile, @Nonnull Set

findVariants(@Nonnull Path theFile, @Nonnull Set alreadyUsedRelatedResources, @Nonnull String[] variants) { + Integer indexOfMainVariant = null; + final String theFileName = theFile.getFileName().toString(); + final String theFileExtension = getFileNameExtension(theFile); + if (NOT_TESTED_LANGUAGES.contains(theFileExtension)) { + return Collections.emptyList(); + } + for (int i = 0; i < variants.length; i++) { + if (theFileName.contains(variants[i])) { + indexOfMainVariant = i; + break; + } + } + if (indexOfMainVariant == null) { + throw new IllegalArgumentException("The file name `" + theFileName + "` must contain one of the variants: " + Arrays.toString(variants) + "!"); + } + + final List variantsFiles = new LinkedList<>(); + for (int i = 0; i < variants.length; i++) { + if (i != indexOfMainVariant) { + final String variant = variants[i]; + final Path variantFile = theFile.getParent().resolve(theFileName.replace(variants[indexOfMainVariant], variant)); + if (!alreadyUsedRelatedResources.contains(variantFile)) { + alreadyUsedRelatedResources.add(variantFile); + variantsFiles.add(variantFile); + } + } + } + + return variantsFiles; + } + /** * This test scans all MarkDown files in the user documentation directory and generates {@link DynamicTest} * instances for each source code block found. Tests are organized in nodes that represents the file they're part @@ -375,10 +419,10 @@ Stream testSingleFileDocumentation() { @Disabled Stream testSingleFileDocumentationAndCreateOtherLanguageSnippets() { return this.createTests( - DocumentationProfile.DEFAULT, - getRootDirectory().resolve("documentation/user/en/query/requirements/telemetry.md"), + DocumentationProfile.LOCALHOST, + getRootDirectory().resolve("documentation/user/en/use/api/query-data.md"), ExampleFilter.values(), - CreateSnippets.MARKDOWN/*, CreateSnippets.JAVA*//*, CreateSnippets.GRAPHQL, CreateSnippets.REST, CreateSnippets.CSHARP*/ + CreateSnippets.MARKDOWN, CreateSnippets.JAVA, CreateSnippets.GRAPHQL, CreateSnippets.REST, CreateSnippets.CSHARP ).stream(); } @@ -523,6 +567,64 @@ private List createTests( } } + final Matcher sourceAlternativeTabsMatcher = SOURCE_ALTERNATIVE_TABS_PATTERN.matcher(fileContent); + while (sourceAlternativeTabsMatcher.find()) { + final Path referencedFile = createPathRelativeToRootDirectory(rootDirectory, sourceAlternativeTabsMatcher.group(2)); + final String referencedFileExtension = getFileNameExtension(referencedFile); + final String[] variants = sourceAlternativeTabsMatcher.group(1).split("\\|"); + if (!NOT_TESTED_LANGUAGES.contains(referencedFileExtension)) { + final List outputSnippet = ofNullable(outputSnippetIndex.get(referencedFile)) + .orElse(Collections.emptyList()); + final CodeSnippet codeSnippet = new CodeSnippet( + "Example `" + referencedFile.getFileName() + "`", + referencedFileExtension, + referencedFile.normalize(), + findVariants(referencedFile, alreadyUsedRelatedResources, variants) + .stream() + .map(relatedFile -> { + final String relatedFileExtension = getFileNameExtension(relatedFile); + return new CodeSnippet( + "Example `" + relatedFile.getFileName() + "`", + relatedFileExtension, + relatedFile.normalize(), + null, + convertToRunnable( + profile, + relatedFileExtension, + readFileOrThrowException(relatedFile), + rootDirectory, + relatedFile, + null, + contextAccessor, + codeSnippetIndex, + ofNullable( + relatedFileExtension.equals("cs") ? + outputSnippetIndex.get(Path.of(relatedFile.toString().replace(".cs", ".evitaql"))) : + outputSnippetIndex.get(relatedFile) + ).orElse(Collections.emptyList()), + createSnippets + ) + ); + }) + .toArray(CodeSnippet[]::new), + convertToRunnable( + profile, + referencedFileExtension, + readFileOrThrowException(referencedFile), + rootDirectory, + referencedFile, + null, + contextAccessor, + codeSnippetIndex, + outputSnippet, + createSnippets + ) + ); + codeSnippets.add(codeSnippet); + codeSnippetIndex.put(codeSnippet.path(), codeSnippet); + } + } + // return tests if some code blocks were found return codeSnippets.isEmpty() ? Collections.emptyList() : diff --git a/evita_functional_tests/src/test/resources/META-INF/documentation/imports.java b/evita_functional_tests/src/test/resources/META-INF/documentation/imports.java index 8b579fc60..fe35799b5 100644 --- a/evita_functional_tests/src/test/resources/META-INF/documentation/imports.java +++ b/evita_functional_tests/src/test/resources/META-INF/documentation/imports.java @@ -45,18 +45,27 @@ import io.evitadb.api.query.order.OrderDirection; import io.evitadb.api.requestResponse.schema.EvolutionMode; import io.evitadb.api.requestResponse.data.annotation.Entity; +import io.evitadb.api.requestResponse.data.annotation.EntityRef; import io.evitadb.api.requestResponse.data.annotation.Attribute; +import io.evitadb.api.requestResponse.data.annotation.AttributeRef; import io.evitadb.api.requestResponse.data.annotation.AssociatedData; +import io.evitadb.api.requestResponse.data.annotation.AssociatedDataRef; +import io.evitadb.api.requestResponse.data.annotation.ParentEntity; import io.evitadb.api.requestResponse.data.annotation.Reference; +import io.evitadb.api.requestResponse.data.annotation.ReferenceRef; import io.evitadb.api.requestResponse.data.annotation.PrimaryKey; +import io.evitadb.api.requestResponse.data.annotation.PrimaryKeyRef; import io.evitadb.api.requestResponse.data.annotation.Price; import io.evitadb.api.requestResponse.data.annotation.PriceForSale; +import io.evitadb.api.requestResponse.data.annotation.PriceForSaleRef; import io.evitadb.api.requestResponse.data.annotation.ReferencedEntity; import io.evitadb.api.requestResponse.data.annotation.ReferencedEntityGroup; -import io.evitadb.api.requestResponse.data.annotation.ParentEntity; +import io.evitadb.api.requestResponse.data.annotation.CreateWhenMissing; +import io.evitadb.api.requestResponse.data.annotation.RemoveWhenExists; import io.evitadb.api.requestResponse.EvitaResponse; import io.evitadb.api.requestResponse.EvitaEntityReferenceResponse; import io.evitadb.api.requestResponse.EvitaEntityResponse; +import io.evitadb.api.requestResponse.data.EntityReferenceContract; import io.evitadb.api.requestResponse.data.structure.EntityReference; import io.evitadb.api.requestResponse.data.structure.InitialEntityBuilder; import io.evitadb.api.requestResponse.data.structure.ExistingEntityBuilder; @@ -73,6 +82,9 @@ import java.time.LocalDateTime; import java.time.LocalDate; import java.time.OffsetDateTime; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.OptionalLong; import java.time.format.DateTimeFormatter; import io.evitadb.dataType.DateTimeRange; import io.evitadb.dataType.IntegerNumberRange; @@ -96,7 +108,9 @@ import javax.annotation.Nullable; import java.io.Serializable; import java.nio.file.Path; +import java.util.Collection; import java.util.List; +import java.util.Set; import java.util.Map; import java.net.URL; import lombok.Data; @@ -120,6 +134,7 @@ import io.netty.handler.ssl.SslContextBuilder; import java.util.concurrent.atomic.AtomicReference; import io.evitadb.api.query.visitor.PrettyPrintingVisitor.StringWithParameters; +import io.evitadb.api.exception.ContextMissingException; import static io.evitadb.api.query.filter.AttributeSpecialValue.*; import static io.evitadb.api.query.require.StatisticsType.*; From 83198c6e686cc4d784941ed5009c972e98848020 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Novotn=C3=BD?= Date: Fri, 8 Dec 2023 21:50:58 +0100 Subject: [PATCH 15/52] fix(#200): correcting documentation test requiring local host environment --- .idea/misc.xml | 7 +- .../en/get-started/create-first-database.md | 52 ++--- .../get-started/example/complete-startup.java | 2 + .../example/create-first-entity.cs | 2 +- .../example/create-first-entity.java | 2 +- .../example/create-small-dataset.cs | 2 +- .../example/create-small-dataset.java | 2 +- .../example/define-catalog-with-schema.cs | 2 +- .../example/define-catalog-with-schema.java | 2 +- .../example/define-catalog.graphql | 2 +- .../get-started/example/define-catalog.rest | 2 +- .../example/define-test-catalog.java | 2 +- .../example/delete-entity-by-pk.cs | 2 +- .../example/delete-entity-by-pk.java | 2 +- .../example/delete-entity-by-query.cs | 2 +- .../example/delete-entity-by-query.java | 2 +- .../example/delete-hierarchical-entity.cs | 2 +- .../example/delete-hierarchical-entity.java | 2 +- .../example/filter-order-entities.cs | 2 +- .../example/filter-order-entities.java | 2 +- .../example/filter-order-products-by-price.cs | 2 +- .../filter-order-products-by-price.java | 2 +- .../en/get-started/example/list-entities.cs | 2 +- .../en/get-started/example/list-entities.java | 2 +- .../example/query-demo-server.evitaql | 2 +- .../get-started/example/read-entity-by-pk.cs | 2 +- .../example/read-entity-by-pk.java | 4 +- .../en/get-started/example/update-entity.cs | 2 +- .../en/get-started/example/update-entity.java | 4 +- .../user/en/get-started/query-our-dataset.md | 8 +- documentation/user/en/query/basics.md | 8 +- .../en/query/examples/complexGrammar.evitaql | 2 +- .../en/query/examples/complexGrammar.graphql | 2 +- .../fetching/referenceContentAll.graphql | 18 ++ .../fetching/referenceContentAll.rest | 17 ++ .../referenceContentAllWithAttributes.graphql | 18 ++ .../referenceContentAllWithAttributes.rest | 17 ++ .../autocloseable-transaction-management.cs | 2 +- .../autocloseable-transaction-management.java | 2 +- .../en/use/api/example/create-new-entity.cs | 2 +- .../en/use/api/example/create-new-entity.java | 2 +- .../declarative-schema-definition.java | 2 +- .../api/example/delete-entities-by-query.cs | 2 +- .../api/example/delete-entities-by-query.java | 2 +- .../en/use/api/example/dry-run-session.cs | 2 +- .../en/use/api/example/dry-run-session.java | 2 +- .../example/finalization-of-warmup-mode.cs | 2 +- .../finalization-of-warmup-mode.graphql | 2 +- .../example/finalization-of-warmup-mode.java | 2 +- .../example/imperative-schema-definition.cs | 2 +- .../example/imperative-schema-definition.java | 2 +- .../example/manual-transaction-management.cs | 2 +- .../manual-transaction-management.java | 2 +- .../api/example/open-session-manually.java | 2 +- .../use/api/example/update-existing-entity.cs | 2 +- .../api/example/update-existing-entity.java | 2 +- documentation/user/en/use/api/query-data.md | 26 +-- documentation/user/en/use/api/schema-api.md | 10 +- documentation/user/en/use/api/write-data.md | 44 ++--- .../examples/custom-contract-reading.java | 2 +- .../examples/custom-contract-writing.java | 2 +- .../sealed-open-lifecycle-example.java | 2 +- documentation/user/en/use/data-types.md | 4 +- ...mentationProfile.java => Environment.java} | 4 +- .../documentation/TestContextFactory.java | 4 +- .../documentation/UserDocumentationTest.java | 178 +++++++++++++----- .../evitadb/documentation/csharp/CShell.java | 10 +- .../csharp/CsharpTestContext.java | 4 +- .../csharp/CsharpTestContextFactory.java | 6 +- .../evitaql/EvitaTestContext.java | 6 +- .../evitaql/EvitaTestContextFactory.java | 6 +- .../graphql/GraphQLTestContext.java | 6 +- .../graphql/GraphQLTestContextFactory.java | 6 +- .../documentation/java/JavaExecutable.java | 107 ++++++----- .../documentation/java/JavaTestContext.java | 4 +- .../java/JavaTestContextFactory.java | 6 +- .../java/JavaWrappingExecutable.java | 121 ++++++++++++ .../documentation/rest/RestTestContext.java | 6 +- .../rest/RestTestContextFactory.java | 6 +- 79 files changed, 557 insertions(+), 258 deletions(-) create mode 100644 documentation/user/en/query/requirements/examples/fetching/referenceContentAll.graphql create mode 100644 documentation/user/en/query/requirements/examples/fetching/referenceContentAll.rest create mode 100644 documentation/user/en/query/requirements/examples/fetching/referenceContentAllWithAttributes.graphql create mode 100644 documentation/user/en/query/requirements/examples/fetching/referenceContentAllWithAttributes.rest rename evita_functional_tests/src/test/java/io/evitadb/documentation/{DocumentationProfile.java => Environment.java} (96%) create mode 100644 evita_functional_tests/src/test/java/io/evitadb/documentation/java/JavaWrappingExecutable.java diff --git a/.idea/misc.xml b/.idea/misc.xml index f59dd3772..8f4f4d9f6 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -24,13 +24,14 @@ +

!codeSnippetIndex.containsKey(it)) .peek(alreadyUsedRelatedResources::add) .toList(); } catch (IOException e) { @@ -308,6 +336,50 @@ private static List findRelatedFiles(@Nonnull Path theFile, @Nonnull Set

findVariants(@Nonnull Path theFile, @Nonnull Set alreadyUsedRelatedResources, @Nonnull String[] variants) { + Integer indexOfMainVariant = null; + final String theFileName = theFile.getFileName().toString(); + final String theFileExtension = getFileNameExtension(theFile); + if (NOT_TESTED_LANGUAGES.contains(theFileExtension)) { + return Collections.emptyList(); + } + for (int i = 0; i < variants.length; i++) { + if (theFileName.contains(variants[i])) { + indexOfMainVariant = i; + break; + } + } + if (indexOfMainVariant == null) { + throw new IllegalArgumentException("The file name `" + theFileName + "` must contain one of the variants: " + Arrays.toString(variants) + "!"); + } + + final List variantsFiles = new LinkedList<>(); + for (int i = 0; i < variants.length; i++) { + if (i != indexOfMainVariant) { + final String variant = variants[i]; + final Path variantFile = theFile.getParent().resolve(theFileName.replace(variants[indexOfMainVariant], variant)).normalize(); + if (!alreadyUsedRelatedResources.contains(variantFile)) { + alreadyUsedRelatedResources.add(variantFile); + variantsFiles.add(variantFile); + } + } + } + + return variantsFiles; + } + + /** + * Creates path relative to the root directory. + */ + @Nonnull + private static Path createPathRelativeToRootDirectory(@Nonnull Path rootDirectory, @Nonnull String path) { + return rootDirectory.resolve(!path.isEmpty() && path.charAt(0) == '/' ? path.substring(1) : path); + } + /** * This test scans all MarkDown files in the user documentation directory and generates {@link DynamicTest} * instances for each source code block found. Tests are organized in nodes that represents the file they're part @@ -323,12 +395,12 @@ Stream testDocumentation() throws IOException { .filter(path -> path.toString().endsWith(".md")) .map(it -> { final List tests = this.createTests( - DocumentationProfile.DEFAULT, + Environment.DEMO_SERVER, it, - new ExampleFilter[] { - ExampleFilter.CSHARP, + new ExampleFilter[]{ + // ExampleFilter.CSHARP, ExampleFilter.JAVA, - ExampleFilter.REST, + // ExampleFilter.REST, ExampleFilter.GRAPHQL, ExampleFilter.EVITAQL } @@ -358,8 +430,8 @@ Stream testDocumentation() throws IOException { @Disabled Stream testSingleFileDocumentation() { return this.createTests( - DocumentationProfile.DEFAULT, - getRootDirectory().resolve("documentation/user/en/use/api/schema-api.md"), + Environment.DEMO_SERVER, + getRootDirectory().resolve("documentation/user/en/get-started/create-first-database.md"), ExampleFilter.values() ).stream(); } @@ -375,8 +447,8 @@ Stream testSingleFileDocumentation() { @Disabled Stream testSingleFileDocumentationAndCreateOtherLanguageSnippets() { return this.createTests( - DocumentationProfile.DEFAULT, - getRootDirectory().resolve("documentation/user/en/query/requirements/telemetry.md"), + Environment.LOCALHOST, + getRootDirectory().resolve("documentation/user/en/use/api/query-data.md"), ExampleFilter.values(), CreateSnippets.MARKDOWN/*, CreateSnippets.JAVA*//*, CreateSnippets.GRAPHQL, CreateSnippets.REST, CreateSnippets.CSHARP*/ ).stream(); @@ -388,7 +460,7 @@ Stream testSingleFileDocumentationAndCreateOtherLanguageSnippets() */ @Nonnull private List createTests( - @Nonnull DocumentationProfile profile, + @Nonnull Environment profile, @Nonnull Path path, @Nonnull ExampleFilter[] exampleFilters, @Nonnull CreateSnippets... createSnippets @@ -458,10 +530,12 @@ private List createTests( while (sourceCodeTabsMatcher.find()) { final Path referencedFile = createPathRelativeToRootDirectory(rootDirectory, sourceCodeTabsMatcher.group(5)); final String referencedFileExtension = getFileNameExtension(referencedFile); - // todo lho: temporary skip testing of source code tab if we dont support its current execution yet if (ofNullable(sourceCodeTabsMatcher.group(2)).map(it -> it.contains("ignoreTest")).orElse(false)) { continue; } + final Environment environment = sourceCodeMatcher.groupCount() > 4 && "local".equals(sourceCodeTabsMatcher.group(4).trim()) ? + Environment.LOCALHOST : profile; + if (!NOT_TESTED_LANGUAGES.contains(referencedFileExtension)) { final Path[] requiredScripts = ofNullable(sourceCodeTabsMatcher.group(2)) .map( @@ -477,36 +551,36 @@ private List createTests( "Example `" + referencedFile.getFileName() + "`", referencedFileExtension, referencedFile.normalize(), - findRelatedFiles(referencedFile, alreadyUsedRelatedResources) + findRelatedFiles(referencedFile, alreadyUsedRelatedResources, codeSnippetIndex) .stream() .map(relatedFile -> { final String relatedFileExtension = getFileNameExtension(relatedFile); return new CodeSnippet( - "Example `" + relatedFile.getFileName() + "`", + "Example `" + relatedFile.getFileName() + "`", + relatedFileExtension, + relatedFile, + null, + convertToRunnable( + environment, relatedFileExtension, - relatedFile.normalize(), - null, - convertToRunnable( - profile, - relatedFileExtension, - readFileOrThrowException(relatedFile), - rootDirectory, - relatedFile, - requiredScripts, - contextAccessor, - codeSnippetIndex, - ofNullable( - relatedFileExtension.equals("cs") ? - outputSnippetIndex.get(Path.of(relatedFile.toString().replace(".cs", ".evitaql"))) : - outputSnippetIndex.get(relatedFile) - ).orElse(Collections.emptyList()), - createSnippets - ) - ); + readFileOrThrowException(relatedFile), + rootDirectory, + relatedFile, + requiredScripts, + contextAccessor, + codeSnippetIndex, + ofNullable( + relatedFileExtension.equals("cs") ? + outputSnippetIndex.get(Path.of(relatedFile.toString().replace(".cs", ".evitaql"))) : + outputSnippetIndex.get(relatedFile) + ).orElse(Collections.emptyList()), + createSnippets + ) + ); }) .toArray(CodeSnippet[]::new), convertToRunnable( - profile, + environment, referencedFileExtension, readFileOrThrowException(referencedFile), rootDirectory, @@ -556,14 +630,6 @@ private List createTests( .toList(); } - /** - * Creates path relative to the root directory. - */ - @Nonnull - private static Path createPathRelativeToRootDirectory(@Nonnull Path rootDirectory, @Nonnull String path) { - return rootDirectory.resolve(!path.isEmpty() && path.charAt(0) == '/' ? path.substring(1) : path); - } - /** * Enum that covers all supported example types that can be run. */ @@ -628,7 +694,7 @@ private static class TestContextProvider { @Getter private final List tearDownTests = new LinkedList<>(); @SuppressWarnings("rawtypes") - private final Map, Supplier> contexts = new HashMap<>(); + private final Map contexts = new HashMap<>(); /** * Provides or creates and stores new instance of {@link Supplier} that provides access to the {@link TestContext} @@ -636,12 +702,13 @@ private static class TestContextProvider { * executed. */ @Nonnull - public > Supplier get(@Nonnull DocumentationProfile profile, @Nonnull Class factoryClass) { + public > Supplier get(@Nonnull Environment profile, @Nonnull Class factoryClass) { //noinspection unchecked return (Supplier) contexts.computeIfAbsent( - factoryClass, - theFactoryClass -> { + new ContextKey(profile, factoryClass), + contextKey -> { try { + final Class theFactoryClass = contextKey.factoryClass(); @SuppressWarnings("unchecked") final TestContextFactory factory = (TestContextFactory) theFactoryClass.getConstructor().newInstance(); ofNullable(factory.getInitTest(profile)).ifPresent(initTests::add); ofNullable(factory.getTearDownTest(profile)).ifPresent(tearDownTests::add); @@ -653,6 +720,19 @@ public > Supplier get( } ); } + + /** + * Cache key for {@link TestContextProvider#contexts}. + * + * @param profile environment + * @param factoryClass factory class + */ + record ContextKey( + @Nonnull Environment profile, + @Nonnull Class factoryClass + ) { + } + } } \ No newline at end of file diff --git a/evita_functional_tests/src/test/java/io/evitadb/documentation/csharp/CShell.java b/evita_functional_tests/src/test/java/io/evitadb/documentation/csharp/CShell.java index 549c830c7..34684a263 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/documentation/csharp/CShell.java +++ b/evita_functional_tests/src/test/java/io/evitadb/documentation/csharp/CShell.java @@ -25,7 +25,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import io.evitadb.documentation.DocumentationProfile; +import io.evitadb.documentation.Environment; import io.evitadb.exception.EvitaInternalError; import javax.annotation.Nonnull; @@ -80,12 +80,12 @@ public class CShell { * Profile of the documentation that is currently being tested - either localhost or the default; the latter represents * evita demo instance. */ - private final DocumentationProfile profile; + private final Environment profile; /** * Constructor for CShell. It downloads the C# query validator executable if it is not present in the temporary folder. */ - public CShell(@Nonnull DocumentationProfile profile) { + public CShell(@Nonnull Environment profile) { this.profile = profile; if (!Files.exists(Paths.get(VALIDATOR_PATH))) { downloadValidator(); @@ -229,10 +229,10 @@ private static void setExecutablePermission() { * encountered during the execution to the standard output */ @Nonnull - private static ProcessBuilder getProcessBuilder(@Nonnull String command, @Nonnull String outputFormat, @Nullable String sourceVariable, @Nonnull DocumentationProfile profile) { + private static ProcessBuilder getProcessBuilder(@Nonnull String command, @Nonnull String outputFormat, @Nullable String sourceVariable, @Nonnull Environment profile) { final ProcessBuilder processBuilder; final String commandToSend = COMPILE.matcher(isWindows() ? command.replace("\"", "\\\"") : command).replaceAll(""); - final String host = profile.equals(DocumentationProfile.LOCALHOST) ? "localhost" : "demo.evitadb.io"; + final String host = profile.equals(Environment.LOCALHOST) ? "localhost" : "demo.evitadb.io"; if (sourceVariable == null) { processBuilder = new ProcessBuilder(VALIDATOR_PATH, commandToSend, host, outputFormat); } else { diff --git a/evita_functional_tests/src/test/java/io/evitadb/documentation/csharp/CsharpTestContext.java b/evita_functional_tests/src/test/java/io/evitadb/documentation/csharp/CsharpTestContext.java index 595b55ed4..79b829c9e 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/documentation/csharp/CsharpTestContext.java +++ b/evita_functional_tests/src/test/java/io/evitadb/documentation/csharp/CsharpTestContext.java @@ -23,7 +23,7 @@ package io.evitadb.documentation.csharp; -import io.evitadb.documentation.DocumentationProfile; +import io.evitadb.documentation.Environment; import io.evitadb.documentation.TestContext; import lombok.Getter; @@ -43,7 +43,7 @@ public class CsharpTestContext implements TestContext { * CShell instance used for C# code validation and fetching results. */ private final CShell cshell; - public CsharpTestContext(@Nonnull DocumentationProfile profile) { + public CsharpTestContext(@Nonnull Environment profile) { this.cshell = new CShell(profile); } } diff --git a/evita_functional_tests/src/test/java/io/evitadb/documentation/csharp/CsharpTestContextFactory.java b/evita_functional_tests/src/test/java/io/evitadb/documentation/csharp/CsharpTestContextFactory.java index a8d25e266..1fcace841 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/documentation/csharp/CsharpTestContextFactory.java +++ b/evita_functional_tests/src/test/java/io/evitadb/documentation/csharp/CsharpTestContextFactory.java @@ -23,7 +23,7 @@ package io.evitadb.documentation.csharp; -import io.evitadb.documentation.DocumentationProfile; +import io.evitadb.documentation.Environment; import io.evitadb.documentation.TestContextFactory; import org.junit.jupiter.api.DynamicTest; @@ -46,7 +46,7 @@ public class CsharpTestContextFactory implements TestContextFactory testContextRef = new AtomicReference<>(); @Nullable @Override - public DynamicTest getInitTest(@Nonnull DocumentationProfile profile) { + public DynamicTest getInitTest(@Nonnull Environment profile) { return dynamicTest( "Init C# query validator (" + profile + ")", () -> testContextRef.set(new CsharpTestContext(profile)) @@ -55,7 +55,7 @@ public DynamicTest getInitTest(@Nonnull DocumentationProfile profile) { @Nullable @Override - public DynamicTest getTearDownTest(@Nonnull DocumentationProfile profile) { + public DynamicTest getTearDownTest(@Nonnull Environment profile) { return dynamicTest( "Destroy C# query validator (" + profile + ")", () -> getContext().getCshell().close() diff --git a/evita_functional_tests/src/test/java/io/evitadb/documentation/evitaql/EvitaTestContext.java b/evita_functional_tests/src/test/java/io/evitadb/documentation/evitaql/EvitaTestContext.java index c8de37609..6be34df07 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/documentation/evitaql/EvitaTestContext.java +++ b/evita_functional_tests/src/test/java/io/evitadb/documentation/evitaql/EvitaTestContext.java @@ -24,7 +24,7 @@ package io.evitadb.documentation.evitaql; import io.evitadb.api.EvitaContract; -import io.evitadb.documentation.DocumentationProfile; +import io.evitadb.documentation.Environment; import io.evitadb.documentation.TestContext; import io.evitadb.driver.EvitaClient; import io.evitadb.driver.config.EvitaClientConfiguration; @@ -56,9 +56,9 @@ public class EvitaTestContext implements TestContext { */ @Nonnull @Getter private final RestQueryConverter restQueryConverter; - public EvitaTestContext(@Nonnull DocumentationProfile profile) { + public EvitaTestContext(@Nonnull Environment profile) { this.evitaContract = new EvitaClient( - profile == DocumentationProfile.LOCALHOST ? + profile == Environment.LOCALHOST ? EvitaClientConfiguration.builder() .host("localhost") .port(5556) diff --git a/evita_functional_tests/src/test/java/io/evitadb/documentation/evitaql/EvitaTestContextFactory.java b/evita_functional_tests/src/test/java/io/evitadb/documentation/evitaql/EvitaTestContextFactory.java index 0e6ff59a3..5a40832b5 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/documentation/evitaql/EvitaTestContextFactory.java +++ b/evita_functional_tests/src/test/java/io/evitadb/documentation/evitaql/EvitaTestContextFactory.java @@ -23,7 +23,7 @@ package io.evitadb.documentation.evitaql; -import io.evitadb.documentation.DocumentationProfile; +import io.evitadb.documentation.Environment; import io.evitadb.documentation.TestContextFactory; import org.junit.jupiter.api.DynamicTest; @@ -45,7 +45,7 @@ public class EvitaTestContextFactory implements TestContextFactory testContextRef = new AtomicReference<>(); @Override - public DynamicTest getInitTest(@Nonnull DocumentationProfile profile) { + public DynamicTest getInitTest(@Nonnull Environment profile) { return dynamicTest( "Init Evita EvitaQL connection (" + profile + ")", () -> testContextRef.set(new EvitaTestContext(profile)) @@ -53,7 +53,7 @@ public DynamicTest getInitTest(@Nonnull DocumentationProfile profile) { } @Override - public DynamicTest getTearDownTest(@Nonnull DocumentationProfile profile) { + public DynamicTest getTearDownTest(@Nonnull Environment profile) { return dynamicTest( "Destroy Evita EvitaQL connection (" + profile + ")", () -> { diff --git a/evita_functional_tests/src/test/java/io/evitadb/documentation/graphql/GraphQLTestContext.java b/evita_functional_tests/src/test/java/io/evitadb/documentation/graphql/GraphQLTestContext.java index fc7246280..7227b2286 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/documentation/graphql/GraphQLTestContext.java +++ b/evita_functional_tests/src/test/java/io/evitadb/documentation/graphql/GraphQLTestContext.java @@ -23,7 +23,7 @@ package io.evitadb.documentation.graphql; -import io.evitadb.documentation.DocumentationProfile; +import io.evitadb.documentation.Environment; import io.evitadb.documentation.TestContext; import io.evitadb.test.client.GraphQLClient; import lombok.Getter; @@ -43,8 +43,8 @@ public class GraphQLTestContext implements TestContext { @Getter private final GraphQLClient graphQLClient; - public GraphQLTestContext(@Nonnull DocumentationProfile profile) { - this.graphQLClient = profile == DocumentationProfile.LOCALHOST ? + public GraphQLTestContext(@Nonnull Environment profile) { + this.graphQLClient = profile == Environment.LOCALHOST ? new GraphQLClient("https://localhost:5555/gql/evita", false) : new GraphQLClient("https://demo.evitadb.io:5555/gql/evita"); } diff --git a/evita_functional_tests/src/test/java/io/evitadb/documentation/graphql/GraphQLTestContextFactory.java b/evita_functional_tests/src/test/java/io/evitadb/documentation/graphql/GraphQLTestContextFactory.java index 7e1a434be..cb71884d3 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/documentation/graphql/GraphQLTestContextFactory.java +++ b/evita_functional_tests/src/test/java/io/evitadb/documentation/graphql/GraphQLTestContextFactory.java @@ -23,7 +23,7 @@ package io.evitadb.documentation.graphql; -import io.evitadb.documentation.DocumentationProfile; +import io.evitadb.documentation.Environment; import io.evitadb.documentation.TestContextFactory; import org.junit.jupiter.api.DynamicTest; @@ -45,7 +45,7 @@ public class GraphQLTestContextFactory implements TestContextFactory testContextRef = new AtomicReference<>(); @Override - public DynamicTest getInitTest(@Nonnull DocumentationProfile profile) { + public DynamicTest getInitTest(@Nonnull Environment profile) { return dynamicTest( "Init Evita GraphQL connection (" + profile + ")", () -> testContextRef.set(new GraphQLTestContext(profile)) @@ -53,7 +53,7 @@ public DynamicTest getInitTest(@Nonnull DocumentationProfile profile) { } @Override - public DynamicTest getTearDownTest(@Nonnull DocumentationProfile profile) { + public DynamicTest getTearDownTest(@Nonnull Environment profile) { return dynamicTest( "Destroy Evita GraphQL connection (" + profile + ")", () -> {} diff --git a/evita_functional_tests/src/test/java/io/evitadb/documentation/java/JavaExecutable.java b/evita_functional_tests/src/test/java/io/evitadb/documentation/java/JavaExecutable.java index db4e386f8..8ff3adfce 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/documentation/java/JavaExecutable.java +++ b/evita_functional_tests/src/test/java/io/evitadb/documentation/java/JavaExecutable.java @@ -73,7 +73,7 @@ public class JavaExecutable implements Executable, EvitaTestSupport { /** * Contains paths of Java files that needs to be executed prior to {@link #sourceContent} */ - private final @Nullable Path[] requires; + private final @Nullable Path[] requiredResources; /** * Contains index of all (so-far) identified code snippets in this document so that we can reuse their source codes. */ @@ -127,7 +127,7 @@ static InvocationResult executeJShellCommands(@Nonnull JShell jShell, @Nonnull L event.snippet().source() ) ); - // it the event contains exception + // it the event contains exception } else if (event.exception() != null) { // it means, that code was successfully compiled, but threw exception upon evaluation exceptions.add( @@ -155,11 +155,45 @@ static InvocationResult executeJShellCommands(@Nonnull JShell jShell, @Nonnull L ); } + /** + * Method reads the {@link #requiredResources} from the file system and returns their contents as a list of + * {@link JShell} commands. + * + * @param jShell the {@link JShell} instance + * @param rootDirectory the root directory of the documentation + * @param requiredResources the list of required resources + * @param codeSnippetIndex the index of all code snippets in the documentation + * @return the list of {@link JShell} commands + */ + @Nonnull + static List composeRequiredBlocks( + @Nonnull JShell jShell, + @Nonnull Path rootDirectory, + @Nonnull Path[] requiredResources, + @Nonnull Map codeSnippetIndex + ) { + final List requiredSnippet = new LinkedList<>(); + for (Path require : requiredResources) { + final CodeSnippet requiredScript = codeSnippetIndex.get(require); + // if the code snippet is not found in the index, it's read from the file system + if (requiredScript == null) { + requiredSnippet.addAll(toJavaSnippets(jShell, UserDocumentationTest.readFileOrThrowException(rootDirectory.resolve(require)))); + } else { + final Executable executable = requiredScript.executableLambda(); + Assert.isTrue(executable instanceof JavaExecutable, "Java example may require only Java executables!"); + requiredSnippet.addAll( + ((JavaExecutable) executable).getSnippets() + ); + } + } + return requiredSnippet; + } + /** * Method clears the tear down snippet and deletes {@link Evita} directory if it was accessed in * test. */ - private static void clearOnTearDown(@Nonnull JShell jShell, @Nonnull Snippet tearDownSnippet) { + static void clearOnTearDown(@Nonnull JShell jShell, @Nonnull Snippet tearDownSnippet) { if (tearDownSnippet instanceof VarSnippet pathDeclaration && "evitaStoragePathToClear".equals(pathDeclaration.name())) { final String stringValue = jShell.varValue(pathDeclaration); final String pathToClear = stringValue.substring(1, stringValue.length() - 1); @@ -175,6 +209,33 @@ private static void clearOnTearDown(@Nonnull JShell jShell, @Nonnull Snippet tea jShell.drop(tearDownSnippet); } + /** + * Method creates list of source code snippets that could be passed to {@link JShell} instance for compilation and + * execution. If the code snippet declares another code snippet via {@link #requiredResources} as predecessor, + * the executable of such predecessor code snippet is prepended to the list of snippets. If such block is not found + * within the same documentation file, it's read from the file system directly. + */ + @Nonnull + private static List composeCodeBlockWithRequiredBlocks( + @Nonnull JShell jShell, + @Nonnull String sourceCode, + @Nonnull Path rootDirectory, + @Nullable Path[] requiredResources, + @Nonnull Map codeSnippetIndex + ) { + if (ArrayUtils.isEmpty(requiredResources)) { + // simply return contents of the code snippet as result + return toJavaSnippets(jShell, sourceCode); + } else { + final List requiredSnippet = composeRequiredBlocks(jShell, rootDirectory, requiredResources, codeSnippetIndex); + // now combine both required and dependent snippets together + return Stream.concat( + requiredSnippet.stream(), + toJavaSnippets(jShell, sourceCode).stream() + ).toList(); + } + } + @Override public void execute() throws Throwable { final JShell jShell = testContextAccessor.get().getJShell(); @@ -223,50 +284,12 @@ public void execute() throws Throwable { public List getSnippets() { if (parsedSnippets == null) { parsedSnippets = composeCodeBlockWithRequiredBlocks( - testContextAccessor.get().getJShell(), sourceContent, codeSnippetIndex + testContextAccessor.get().getJShell(), sourceContent, getRootDirectory(), requiredResources, codeSnippetIndex ); } return parsedSnippets; } - /** - * Method creates list of source code snippets that could be passed to {@link JShell} instance for compilation and - * execution. If the code snippet declares another code snippet via {@link #requires} as predecessor, - * the executable of such predecessor code snippet is prepended to the list of snippets. If such block is not found - * within the same documentation file, it's read from the file system directly. - */ - @Nonnull - private List composeCodeBlockWithRequiredBlocks( - @Nonnull JShell jShell, - @Nonnull String sourceCode, - @Nonnull Map codeSnippetIndex - ) { - if (ArrayUtils.isEmpty(requires)) { - // simply return contents of the code snippet as result - return toJavaSnippets(jShell, sourceCode); - } else { - final List requiredSnippet = new LinkedList<>(); - for (Path require : requires) { - final CodeSnippet requiredScript = codeSnippetIndex.get(require); - // if the code snippet is not found in the index, it's read from the file system - if (requiredScript == null) { - requiredSnippet.addAll(toJavaSnippets(jShell, UserDocumentationTest.readFileOrThrowException(getRootDirectory().resolve(require)))); - } else { - final Executable executable = requiredScript.executableLambda(); - Assert.isTrue(executable instanceof JavaExecutable, "Java example may require only Java executables!"); - requiredSnippet.addAll( - ((JavaExecutable) executable).getSnippets() - ); - } - } - // now combine both required and dependent snippets together - return Stream.concat( - requiredSnippet.stream(), - toJavaSnippets(jShell, sourceCode).stream() - ).toList(); - } - } - /** * Record contains result of the Java code execution. */ diff --git a/evita_functional_tests/src/test/java/io/evitadb/documentation/java/JavaTestContext.java b/evita_functional_tests/src/test/java/io/evitadb/documentation/java/JavaTestContext.java index b52763408..f08ed713a 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/documentation/java/JavaTestContext.java +++ b/evita_functional_tests/src/test/java/io/evitadb/documentation/java/JavaTestContext.java @@ -23,7 +23,7 @@ package io.evitadb.documentation.java; -import io.evitadb.documentation.DocumentationProfile; +import io.evitadb.documentation.Environment; import io.evitadb.documentation.TestContext; import io.evitadb.documentation.UserDocumentationTest; import io.evitadb.documentation.java.JavaExecutable.InvocationResult; @@ -74,7 +74,7 @@ public class JavaTestContext implements TestContext { @Getter private final JShell jShell; - public JavaTestContext(@Nonnull DocumentationProfile profile) { + public JavaTestContext(@Nonnull Environment profile) { this.jShell = JShell.builder() // this is faster because JVM is not forked for each test .executionEngine(new LocalExecutionControlProvider(), Collections.emptyMap()) diff --git a/evita_functional_tests/src/test/java/io/evitadb/documentation/java/JavaTestContextFactory.java b/evita_functional_tests/src/test/java/io/evitadb/documentation/java/JavaTestContextFactory.java index 5b67ad238..b5d8dc12c 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/documentation/java/JavaTestContextFactory.java +++ b/evita_functional_tests/src/test/java/io/evitadb/documentation/java/JavaTestContextFactory.java @@ -23,7 +23,7 @@ package io.evitadb.documentation.java; -import io.evitadb.documentation.DocumentationProfile; +import io.evitadb.documentation.Environment; import io.evitadb.documentation.TestContextFactory; import org.junit.jupiter.api.DynamicTest; @@ -45,7 +45,7 @@ public class JavaTestContextFactory implements TestContextFactory testContextRef = new AtomicReference<>(); @Override - public DynamicTest getInitTest(@Nonnull DocumentationProfile profile) { + public DynamicTest getInitTest(@Nonnull Environment profile) { return dynamicTest( "Init JShell (" + profile + ")", () -> testContextRef.set(new JavaTestContext(profile)) @@ -53,7 +53,7 @@ public DynamicTest getInitTest(@Nonnull DocumentationProfile profile) { } @Override - public DynamicTest getTearDownTest(@Nonnull DocumentationProfile profile) { + public DynamicTest getTearDownTest(@Nonnull Environment profile) { return dynamicTest( "Destroy JShell (" + profile + ")", () -> getContext().getJShell().close() diff --git a/evita_functional_tests/src/test/java/io/evitadb/documentation/java/JavaWrappingExecutable.java b/evita_functional_tests/src/test/java/io/evitadb/documentation/java/JavaWrappingExecutable.java new file mode 100644 index 000000000..7078d8bad --- /dev/null +++ b/evita_functional_tests/src/test/java/io/evitadb/documentation/java/JavaWrappingExecutable.java @@ -0,0 +1,121 @@ +/* + * + * _ _ ____ ____ + * _____ _(_) |_ __ _| _ \| __ ) + * / _ \ \ / / | __/ _` | | | | _ \ + * | __/\ V /| | || (_| | |_| | |_) | + * \___| \_/ |_|\__\__,_|____/|____/ + * + * Copyright (c) 2023 + * + * 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/main/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.documentation.java; + +import io.evitadb.documentation.UserDocumentationTest.CodeSnippet; +import io.evitadb.documentation.java.JavaExecutable.InvocationResult; +import io.evitadb.test.EvitaTestSupport; +import jdk.jshell.JShell; +import jdk.jshell.Snippet; +import jdk.jshell.VarSnippet; +import lombok.RequiredArgsConstructor; +import org.junit.jupiter.api.function.Executable; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +import static io.evitadb.documentation.java.JavaExecutable.clearOnTearDown; +import static io.evitadb.documentation.java.JavaExecutable.composeRequiredBlocks; +import static io.evitadb.documentation.java.JavaExecutable.executeJShellCommands; + +/** + * This class allows to execute some Java code before and after the actual test. The code is executed in the same + * process as the test itself, so it can be used to prepare the environment for the test. + * + * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2023 + */ +@RequiredArgsConstructor +public class JavaWrappingExecutable implements Executable, EvitaTestSupport { + /** + * Provides access to the {@link JavaTestContext} instance. + */ + private final @Nonnull Supplier testContextAccessor; + /** + * Contains the executable that should be invoked after all required resources are executed, and before they are + * torn down. + */ + @Nonnull private final Executable delegate; + /** + * Contains paths of Java files that needs to be executed prior to {@link #delegate} + */ + private final @Nullable Path[] requiredResources; + /** + * Contains index of all (so-far) identified code snippets in this document so that we can reuse their source codes. + */ + private final @Nonnull Map codeSnippetIndex; + + @Override + public void execute() throws Throwable { + final JShell jShell = testContextAccessor.get().getJShell(); + final InvocationResult result = executeJShellCommands( + jShell, + composeRequiredBlocks(jShell, getRootDirectory(), requiredResources, codeSnippetIndex) + ); + + try { + if (result.exception() == null) { + delegate.execute(); + } + } finally { + // clean up - we travel from the most recent (last) snippet to the first + final List snippets = result.snippets(); + for (int i = snippets.size() - 1; i >= 0; i--) { + final Snippet snippet = snippets.get(i); + // if the snippet declared an AutoCloseable variable, we need to close it + if (snippet instanceof VarSnippet varSnippet) { + // there is no way how to get the reference of the variable - so the clean up + // must be performed by another snippet + executeJShellCommands( + jShell, + Arrays.asList( + // instanceof / cast throws a compiler exception, so that we need to + // work around it by runtime evaluation + "if (AutoCloseable.class.isInstance(" + varSnippet.name() + ")) {\n\t" + + "AutoCloseable.class.cast(" + varSnippet.name() + ").close();\n" + + "}\n", + // retrieve the folder location + "String evitaStoragePathToClear = Evita.class.isInstance(" + varSnippet.name() + ") ? " + + "Evita.class.cast(" + varSnippet.name() + ").getConfiguration().storage()" + + ".storageDirectory().toAbsolutePath().toString() : \"\";\n" + ) + ) + .snippets() + .forEach(it -> clearOnTearDown(jShell, it)); + } + // each snippet is "dropped" by the JShell instance (undone) + jShell.drop(snippet); + } + + if (result.exception() != null) { + throw result.exception(); + } + } + } + +} diff --git a/evita_functional_tests/src/test/java/io/evitadb/documentation/rest/RestTestContext.java b/evita_functional_tests/src/test/java/io/evitadb/documentation/rest/RestTestContext.java index 4d010d488..a5d610c06 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/documentation/rest/RestTestContext.java +++ b/evita_functional_tests/src/test/java/io/evitadb/documentation/rest/RestTestContext.java @@ -23,7 +23,7 @@ package io.evitadb.documentation.rest; -import io.evitadb.documentation.DocumentationProfile; +import io.evitadb.documentation.Environment; import io.evitadb.documentation.TestContext; import io.evitadb.test.client.RestClient; import lombok.Getter; @@ -43,8 +43,8 @@ public class RestTestContext implements TestContext { @Getter private final RestClient restClient; - public RestTestContext(@Nonnull DocumentationProfile profile) { - this.restClient = profile == DocumentationProfile.LOCALHOST ? + public RestTestContext(@Nonnull Environment profile) { + this.restClient = profile == Environment.LOCALHOST ? new RestClient("https://localhost:5555", false) : new RestClient("https://demo.evitadb.io:5555"); } diff --git a/evita_functional_tests/src/test/java/io/evitadb/documentation/rest/RestTestContextFactory.java b/evita_functional_tests/src/test/java/io/evitadb/documentation/rest/RestTestContextFactory.java index ea098ad57..e3efb04ef 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/documentation/rest/RestTestContextFactory.java +++ b/evita_functional_tests/src/test/java/io/evitadb/documentation/rest/RestTestContextFactory.java @@ -23,7 +23,7 @@ package io.evitadb.documentation.rest; -import io.evitadb.documentation.DocumentationProfile; +import io.evitadb.documentation.Environment; import io.evitadb.documentation.TestContextFactory; import org.junit.jupiter.api.DynamicTest; @@ -45,7 +45,7 @@ public class RestTestContextFactory implements TestContextFactory testContextRef = new AtomicReference<>(); @Override - public DynamicTest getInitTest(@Nonnull DocumentationProfile profile) { + public DynamicTest getInitTest(@Nonnull Environment profile) { return dynamicTest( "Init Evita REST connection (" + profile + ")", () -> testContextRef.set(new RestTestContext(profile)) @@ -53,7 +53,7 @@ public DynamicTest getInitTest(@Nonnull DocumentationProfile profile) { } @Override - public DynamicTest getTearDownTest(@Nonnull DocumentationProfile profile) { + public DynamicTest getTearDownTest(@Nonnull Environment profile) { return dynamicTest( "Destroy Evita REST connection (" + profile + ")", () -> {} From e1b5ae45644833e3f668fce426255d7878535e55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Novotn=C3=BD?= Date: Mon, 11 Dec 2023 07:14:45 +0100 Subject: [PATCH 16/52] test: fixed NullPointerException --- .../src/main/java/io/evitadb/index/attribute/SortIndex.java | 2 +- .../src/test/java/io/evitadb/index/attribute/SortIndexTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/evita_engine/src/main/java/io/evitadb/index/attribute/SortIndex.java b/evita_engine/src/main/java/io/evitadb/index/attribute/SortIndex.java index 9eadfc60b..ef25a46da 100644 --- a/evita_engine/src/main/java/io/evitadb/index/attribute/SortIndex.java +++ b/evita_engine/src/main/java/io/evitadb/index/attribute/SortIndex.java @@ -186,7 +186,7 @@ private static Comparator createCombinedComparatorFor(@Nonnull Locale locale, */ @SuppressWarnings("rawtypes") @Nonnull - private static Comparator createComparatorFor(@Nonnull Locale locale, @Nonnull ComparatorSource comparatorSource) { + private static Comparator createComparatorFor(@Nullable Locale locale, @Nonnull ComparatorSource comparatorSource) { final Comparator nextComparator = String.class.isAssignableFrom(comparatorSource.type()) ? ofNullable(locale) .map(it -> { diff --git a/evita_functional_tests/src/test/java/io/evitadb/index/attribute/SortIndexTest.java b/evita_functional_tests/src/test/java/io/evitadb/index/attribute/SortIndexTest.java index c0788357d..810dc2b59 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/index/attribute/SortIndexTest.java +++ b/evita_functional_tests/src/test/java/io/evitadb/index/attribute/SortIndexTest.java @@ -238,7 +238,7 @@ void generationalProofTest(GenerationalTestInput input) { runFor( input, 1_000, - new TestState(new StringBuilder(), new SortIndex(String.class, null)), + new TestState(new StringBuilder(), new SortIndex(String.class, new AttributeKey("whatever"))), (random, testState) -> { final StringBuilder ops = testState.code(); ops.append("final SortIndex sortIndex = new SortIndex(String.class);\n") From 311cae3e0c17f2a011d8307f47bf928875c77c9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Mon, 11 Dec 2023 09:54:32 +0100 Subject: [PATCH 17/52] docs(#200): fix `local` parameter checking, add missing local parameters --- .../en/get-started/create-first-database.md | 20 +++++++++---------- .../documentation/UserDocumentationTest.java | 3 ++- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/documentation/user/en/get-started/create-first-database.md b/documentation/user/en/get-started/create-first-database.md index cddb16d5c..4ad3ad258 100644 --- a/documentation/user/en/get-started/create-first-database.md +++ b/documentation/user/en/get-started/create-first-database.md @@ -156,7 +156,7 @@ the load in the cluster. Let's see how you can retrieve the entity you just created in another read-only session via the same catalog data API as mentioned above. - + [Example of reading an entity by primary key](/documentation/user/en/get-started/example/read-entity-by-pk.graphql) @@ -184,7 +184,7 @@ the load in the cluster. Let's see how you can retrieve the entity you just created in another read-only session via the same catalog data API as mentioned above. - + [Example of reading an entity by primary key](/documentation/user/en/get-started/example/read-entity-by-pk.rest) @@ -213,21 +213,21 @@ have in the relational database. The example shows how to define attributes, ass To get a better idea of the data, let's list the existing entities from the database. - + [Example of listing entities](/documentation/user/en/get-started/example/list-entities.java) You can also filter and sort the data: - + [Example of filtering and ordering entities](/documentation/user/en/get-started/example/filter-order-entities.java) Or you can filter all products by price in EUR greater than €300 and order by price with the cheapest products first: - + [Example of filtering and ordering products by price](/documentation/user/en/get-started/example/filter-order-products-by-price.java) @@ -240,7 +240,7 @@ Or you can filter all products by price in EUR greater than €300 and order by Updating an entity is similar to creating a new entity: - + [Example of listing entities](/documentation/user/en/get-started/example/update-entity.java) @@ -260,7 +260,7 @@ For more information, see the [write API description](../use/api/write-data.md#u Updating an entity is similar to creating a new entity: - + [Example of listing entities](/documentation/user/en/get-started/example/update-entity.graphql) @@ -277,7 +277,7 @@ For more information, see the [write API description](../use/api/write-data.md#u Updating an entity is similar to creating a new entity: - + [Example of listing entities](/documentation/user/en/get-started/example/update-entity.rest) @@ -326,13 +326,13 @@ Or, you can issue a query that removes all the entities that match the query: When you delete a hierarchical entity, you can choose whether or not to delete it with all of its child entities: - + [Example of deleting hierarchical entity](/documentation/user/en/get-started/example/delete-hierarchical-entity.java) - + [Example of deleting hierarchical entity](/documentation/user/en/get-started/example/delete-hierarchical-entity.cs) 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 7edfb37ab..961c5ed89 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 @@ -533,7 +533,8 @@ private List createTests( if (ofNullable(sourceCodeTabsMatcher.group(2)).map(it -> it.contains("ignoreTest")).orElse(false)) { continue; } - final Environment environment = sourceCodeMatcher.groupCount() > 4 && "local".equals(sourceCodeTabsMatcher.group(4).trim()) ? + final String localParameter = sourceCodeTabsMatcher.group(4); + final Environment environment = localParameter != null && "local".equals(localParameter.trim()) ? Environment.LOCALHOST : profile; if (!NOT_TESTED_LANGUAGES.contains(referencedFileExtension)) { From 23f7f17add326a6083ebc2fd061444950735fcb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Mon, 11 Dec 2023 13:57:38 +0100 Subject: [PATCH 18/52] docs(#200): fix GQL/REST write several examples --- .../en/get-started/create-first-database.md | 26 +-- .../example/read-entity-by-pk.java | 25 ++- ...perative-catalog-schema-definition.graphql | 4 +- documentation/user/en/use/api/write-data.md | 194 +++++++++--------- .../api/catalog/CatalogGraphQLBuilder.java | 4 +- .../EvitaSessionManagingInstrumentation.java | 16 +- .../DeleteEntitiesMutatingDataFetcher.java | 3 +- .../documentation/UserDocumentationTest.java | 5 +- .../graphql/GraphQLExecutable.java | 23 ++- .../graphql/GraphQLTestContext.java | 4 +- .../io/evitadb/test/client/GraphQLClient.java | 27 ++- 11 files changed, 194 insertions(+), 137 deletions(-) diff --git a/documentation/user/en/get-started/create-first-database.md b/documentation/user/en/get-started/create-first-database.md index 4ad3ad258..cc583ee97 100644 --- a/documentation/user/en/get-started/create-first-database.md +++ b/documentation/user/en/get-started/create-first-database.md @@ -66,7 +66,7 @@ that forms a tree, product is enabled to have prices: Now you can use the [system API](/documentation/user/en/use/connectors/graphql.md#graphql-api-instances) via the URL `https://your-server:5555/gql/system` to create a new empty catalog: - + [Example of creating empty catalog](/documentation/user/en/get-started/example/define-catalog.graphql) @@ -77,7 +77,7 @@ modifying its schema via the [catalog schema API](/documentation/user/en/use/con contains some attributes (either localized or non-localized), category is marked as a hierarchical entity that forms a tree, product is enabled to have prices: - + [Example of creating empty catalog](/documentation/user/en/get-started/example/define-schema-for-catalog.graphql) @@ -91,7 +91,7 @@ a tree, product is enabled to have prices: Now you can use the [system API](/documentation/user/en/use/connectors/rest.md#rest-api-instances) via the URL `https://your-server:5555/rest/system/catalogs` to create a new empty catalog: - + [Example of creating empty catalog](/documentation/user/en/get-started/example/define-catalog.rest) @@ -102,7 +102,7 @@ modifying its schema via the [catalog schema API](/documentation/user/en/use/con contains some attributes (either localized or non-localized), category is marked as a hierarchical entity that forms a tree, product is enabled to have prices: - + [Example of creating empty catalog](/documentation/user/en/get-started/example/define-schema-for-catalog.rest) @@ -115,7 +115,7 @@ a tree, product is enabled to have prices: Once the catalog is created and the schema is known, you can insert a first entity to the catalog: - + [Example of inserting an entity](/documentation/user/en/get-started/example/create-first-entity.java) @@ -142,7 +142,7 @@ Once the catalog is created and the schema is known, you can insert a first enti [catalog data API](/documentation/user/en/use/connectors/graphql.md#graphql-api-instances) at the `https://your-server:5555/gql/test-catalog` URL: - + [Example of inserting an entity](/documentation/user/en/get-started/example/create-first-entity.graphql) @@ -170,7 +170,7 @@ Once the catalog is created and the schema is known, you can insert a first enti [catalog data API](/documentation/user/en/use/connectors/rest.md#rest-api-instances) at the URL `https://your-server:5555/rest/test-catalog/brand`: - + [Example of inserting an entity](/documentation/user/en/get-started/example/create-first-entity.rest) @@ -197,7 +197,7 @@ as mentioned above. Once you learn the basics, you can create a small dataset to work with: - + [Example of creating a small dataset](/documentation/user/en/get-started/example/create-small-dataset.java) @@ -260,7 +260,7 @@ For more information, see the [write API description](../use/api/write-data.md#u Updating an entity is similar to creating a new entity: - + [Example of listing entities](/documentation/user/en/get-started/example/update-entity.graphql) @@ -277,7 +277,7 @@ For more information, see the [write API description](../use/api/write-data.md#u Updating an entity is similar to creating a new entity: - + [Example of listing entities](/documentation/user/en/get-started/example/update-entity.rest) @@ -349,7 +349,7 @@ For more complex examples and explanations, see the [write API chapter](../use/a You can issue a query that removes all the entities that match the query using the same catalog data API that you would use to insert, update or retrieve entities: - + [Example of deleting entity by query](/documentation/user/en/get-started/example/delete-entity-by-query.graphql) @@ -363,7 +363,7 @@ For more complex examples and explanations, see the [write API chapter](../use/a You can delete entity by is primary key: - + [Example of deleting entity by PK](/documentation/user/en/get-started/example/delete-entity-by-pk.rest) @@ -371,7 +371,7 @@ You can delete entity by is primary key: Or, you can issue a query that removes all the entities that match the query using the same catalog data API that you would use to insert, update or retrieve entities: - + [Example of deleting entity by query](/documentation/user/en/get-started/example/delete-entity-by-query.rest) diff --git a/documentation/user/en/get-started/example/read-entity-by-pk.java b/documentation/user/en/get-started/example/read-entity-by-pk.java index c37d6490b..0405b0abc 100644 --- a/documentation/user/en/get-started/example/read-entity-by-pk.java +++ b/documentation/user/en/get-started/example/read-entity-by-pk.java @@ -1,7 +1,30 @@ +/* + * + * _ _ ____ ____ + * _____ _(_) |_ __ _| _ \| __ ) + * / _ \ \ / / | __/ _` | | | | _ \ + * | __/\ V /| | || (_| | |_| | |_) | + * \___| \_/ |_|\__\__,_|____/|____/ + * + * Copyright (c) 2023 + * + * 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/main/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 String brandNameInEnglish = evita.queryCatalog( "evita", session -> { - return session.getEntity("Brand", 1, attributeContent("name"), dataInLocales(Locale.ENGLISH)) + return session.getEntity("Brand", 1, attributeContent("name")) .orElseThrow() .getAttribute("name"); } diff --git a/documentation/user/en/use/api/example/imperative-catalog-schema-definition.graphql b/documentation/user/en/use/api/example/imperative-catalog-schema-definition.graphql index 1c81e00d2..736cbf1e0 100644 --- a/documentation/user/en/use/api/example/imperative-catalog-schema-definition.graphql +++ b/documentation/user/en/use/api/example/imperative-catalog-schema-definition.graphql @@ -55,14 +55,14 @@ mutation { createAttributeSchemaMutation: { name: "code", type: String - unique: true + uniquenessType: UNIQUE_WITHIN_COLLECTION } }, { createAttributeSchemaMutation: { name: "url" type: String - unique: true + uniquenessType: UNIQUE_WITHIN_COLLECTION localized: true } }, diff --git a/documentation/user/en/use/api/write-data.md b/documentation/user/en/use/api/write-data.md index 3ac40c8fd..677ad2123 100644 --- a/documentation/user/en/use/api/write-data.md +++ b/documentation/user/en/use/api/write-data.md @@ -1,7 +1,7 @@ --- title: Write data perex: | - This article contains the main principles for authoring data in evitaDB, the description of the data API regarding + This article contains the main principles for authoring data in evitaDB, the description of the data API regarding entity upsert and deletion and related recommendations. date: '17.1.2023' author: 'Ing. Jan Novotný' @@ -13,10 +13,10 @@ preferredLang: 'java' ## Indexing modes -evitaDB assumes that it will not be the primary data store for your data. Because evitaDB is a relatively new database -implementation, it's wise to store your primary data in a mature, time-tested and proven technology such as a relational -database. evitaDB brings you the necessary low-latency and e-commerce-optimized feature as a secondary fast-read index -where you mirror/transform the data from your primary data store. We would like to become your primary data store one +evitaDB assumes that it will not be the primary data store for your data. Because evitaDB is a relatively new database +implementation, it's wise to store your primary data in a mature, time-tested and proven technology such as a relational +database. evitaDB brings you the necessary low-latency and e-commerce-optimized feature as a secondary fast-read index +where you mirror/transform the data from your primary data store. We would like to become your primary data store one day, but let's be honest - we're not there yet. This reasoning led us to design two different types of [entity data](../data-model.md) ingestion and corresponding @@ -27,15 +27,15 @@ catalog states: ### Bulk indexing -Bulk indexing is used to quickly index large amounts of source data. It's used for initial catalog creation from -external (primary) data stores. It doesn't need to support transactions and allows only a single session (single thread) -to be opened from the client side. The catalog is in a so-called `WARMUP` state -(evita_api/src/main/java/io/evitadb/api/CatalogState.java). The client can both write and -query the written data, but no other client can open another session because the consistency of the data could not be +Bulk indexing is used to quickly index large amounts of source data. It's used for initial catalog creation from +external (primary) data stores. It doesn't need to support transactions and allows only a single session (single thread) +to be opened from the client side. The catalog is in a so-called `WARMUP` state +(evita_api/src/main/java/io/evitadb/api/CatalogState.java). The client can both write and +query the written data, but no other client can open another session because the consistency of the data could not be guaranteed for them. The goal here is to index hundreds or thousands of entities per second. -If the database crashes during this initial bulk indexing, the state and consistency of the data must be considered -corrupted, and the entire catalog should be dumped and rebuilt from scratch. Since there is no client other than the +If the database crashes during this initial bulk indexing, the state and consistency of the data must be considered +corrupted, and the entire catalog should be dumped and rebuilt from scratch. Since there is no client other than the one writing the data, we can afford to do this. @@ -79,7 +79,7 @@ Any newly created catalog starts in `WARMUP` state and must be manually switched [Termination of warm-up mode](/documentation/user/en/use/api/example/finalization-of-warmup-mode.java) -The `goLiveAndClose` method sets the catalog to `ALIVE` (transactional) state and closes the current session. From this +The `goLiveAndClose` method sets the catalog to `ALIVE` (transactional) state and closes the current session. From this moment on, multiple clients can open read-only or read-write sessions in parallel to this particular catalog. @@ -98,7 +98,7 @@ moment on, multiple clients can open read-only or read-write sessions in paralle -Any newly created catalog starts in `WARMUP` state and must be manually switched to *transactional* mode using the +Any newly created catalog starts in `WARMUP` state and must be manually switched to *transactional* mode using the [system API](/documentation/user/en/use/connectors/graphql.md#graphql-api-instances) by executing: @@ -130,14 +130,14 @@ moment on, multiple clients can send fetching or mutating requests in parallel t ### Incremental indexing The incremental indexing mode is used to keep the index up to date with the primary data store during its lifetime. -We expect some form of [change data capture](https://en.wikipedia.org/wiki/Change_data_capture) process to be built into -the primary data store. One of the more interesting recent developments in this area is -[the Debezium project](https://debezium.io/), which allows changes from primary data stores to be streamed to secondary +We expect some form of [change data capture](https://en.wikipedia.org/wiki/Change_data_capture) process to be built into +the primary data store. One of the more interesting recent developments in this area is +[the Debezium project](https://debezium.io/), which allows changes from primary data stores to be streamed to secondary indexes fairly easily. There might be multiple clients reading & writing data to the same catalog when it is in `ALIVE` state. Each catalog -update is wrapped into a *transaction* that meets -[the snapshot isolation level](https://en.wikipedia.org/wiki/Snapshot_isolation). More details about transaction +update is wrapped into a *transaction* that meets +[the snapshot isolation level](https://en.wikipedia.org/wiki/Snapshot_isolation). More details about transaction handling is in [separate chapter](../../deep-dive/transactions.md). ## Model characteristics @@ -154,7 +154,7 @@ concurrent access (in other words, entities can be cached without fear of race c -All model classes are described by interfaces, and there should be no reason to use or instantiate direct classes. +All model classes are described by interfaces, and there should be no reason to use or instantiate direct classes. Interfaces follow this structure:

@@ -166,7 +166,7 @@ Interfaces follow this structure:
combines **Contract** and **Editor** interfaces, and it is actually used to create the instance
-When you create new entity using evitaDB API, you obtain a builder, and you can immediately start setting the data +When you create new entity using evitaDB API, you obtain a builder, and you can immediately start setting the data to the entity and then store the entity to the database: @@ -182,7 +182,7 @@ to the entity and then store the entity to the database: -When you read existing entity from the catalog, you obtain read-only +When you read existing entity from the catalog, you obtain read-only evita_api/src/main/java/io/evitadb/api/requestResponse/data/SealedEntity.javaEvitaDB.Client/Models/Data/ISealedEntity.cs, which is basically a contract interface with a few methods allowing you to convert it to the builder instance that can be used for updating the data: @@ -208,7 +208,7 @@ In the GraphQL API, the immutability is implicit by design. You may be able to m application, but these changes cannot be propagated to the evitaDB server, so it is recommended to make your client model immutable as well (see the Java API for inspiration). The only way to modify the data is to use the [catalog data API](/documentation/user/en/use/connectors/graphql.md#graphql-api-instances) and manually send evitaDB mutations -with individual changes using one of the `updateCollectionName` GraphQL mutations specific to your selected +with individual changes using one of the `updateCollectionName` GraphQL mutations specific to your selected [entity collection](/documentation/user/en/use/data-model.md#collection). @@ -227,7 +227,7 @@ with individual changes using one of the REST endpoints for modifying data of yo ### Versioning -All model classes are versioned - in other words, when a model instance is modified, the version number of the new +All model classes are versioned - in other words, when a model instance is modified, the version number of the new instance created from that modified state is incremented by one. @@ -235,7 +235,7 @@ instance created from that modified state is incremented by one. Version information is available not only at the evita_api/src/main/java/io/evitadb/api/requestResponse/data/EntityContract.javaEvitaDB.Client/Models/Data/IEntity.cs level, -but also at more granular levels (such as +but also at more granular levels (such as evita_api/src/main/java/io/evitadb/api/requestResponse/data/AttributesContract.javaEvitaDB.Client/Models/Data/IAttributes.cs, evita_api/src/main/java/io/evitadb/api/requestResponse/data/ReferenceContract.javaEvitaDB.Client/Models/Data/IReference.cs, or evita_api/src/main/java/io/evitadb/api/requestResponse/data/AssociatedDataContract.javaEvitaDB.Client/Models/Data/IAssociatedData.cs). @@ -245,7 +245,7 @@ All model classes that support versioning implement the -Version information is available at the entity level. +Version information is available at the entity level. @@ -264,7 +264,7 @@ The version information serves two purposes: -Since the entity is *immutable* and *versioned* the default implementation of the `hashCode` and `equals` takes these +Since the entity is *immutable* and *versioned* the default implementation of the `hashCode` and `equals` takes these three components into account: 1. entity type @@ -290,19 +290,19 @@ interface and implemented by the The communication with the evitaDB instance always takes place via the evita_api/src/main/java/io/evitadb/api/EvitaSessionContract.javaEvitaDB.Client/EvitaClientSession.cs interface. -Session is a single-threaded communication channel identified by a unique +Session is a single-threaded communication channel identified by a unique [random UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier). -In the web environment it's a good idea to have one session per request, in batch processing it's recommended to keep +In the web environment it's a good idea to have one session per request, in batch processing it's recommended to keep a single session for an entire batch. -To conserve resources, the server automatically closes sessions after a period of inactivity. -The interval is set by default to `60 seconds` but +To conserve resources, the server automatically closes sessions after a period of inactivity. +The interval is set by default to `60 seconds` but [it can be changed](https://evitadb.io/documentation/operate/configure#server-configuration) to different value. The inactivity means that there is no activity recorded on the evita_api/src/main/java/io/evitadb/api/EvitaSessionContract.javaEvitaDB.Client/EvitaClientSession.cs interface. If you need -to artificially keep session alive you need to periodically call some method without side-effects on the session +to artificially keep session alive you need to periodically call some method without side-effects on the session interface, such as:
@@ -310,7 +310,7 @@ interface, such as:
In case of embedded evitaDB usage.
`getEntityCollectionSize`
- In case of remote use of evitaDB. In this case we really need to call some method that triggers the network + In case of remote use of evitaDB. In this case we really need to call some method that triggers the network communication. Many methods in evita_api/src/main/java/io/evitadb/api/EvitaSessionContract.javaEvitaDB.Client/EvitaClientSession.cs return only locally cached results to avoid expensive and unnecessary network calls. @@ -319,11 +319,11 @@ interface, such as: evita_api/src/main/java/io/evitadb/api/TransactionContract.javaEvitaDB.Client/EvitaClientTransaction.cs is an envelope for a "unit -of work" with evitaDB. A transaction exists within a session and is guaranteed to have -[the snapshot isolation level](https://en.wikipedia.org/wiki/Snapshot_isolation) for reads. The changes in -a transaction are always isolated from other transactions and become visible only after the transaction has been -committed. If the transaction is marked as *rollback only*, all changes will be discarded on transaction closing and -will never reach the shared database state. There can be at most one active transaction in a session, but there can +of work" with evitaDB. A transaction exists within a session and is guaranteed to have +[the snapshot isolation level](https://en.wikipedia.org/wiki/Snapshot_isolation) for reads. The changes in +a transaction are always isolated from other transactions and become visible only after the transaction has been +committed. If the transaction is marked as *rollback only*, all changes will be discarded on transaction closing and +will never reach the shared database state. There can be at most one active transaction in a session, but there can be multiple successor transactions during the session's lifetime. @@ -333,11 +333,11 @@ be multiple successor transactions during the session's lifetime. The communication with the evitaDB instance using the GraphQL API always uses some kind of session. In the case of the GraphQL API, a session is a per-request communication channel that is used in the background. -A transaction is an envelope for a "unit of work" with evitaDB. -In the GraphQL API, a transaction exists for the duration of a session, or more precisely a GraphQL API request, and it is +A transaction is an envelope for a "unit of work" with evitaDB. +In the GraphQL API, a transaction exists for the duration of a session, or more precisely a GraphQL API request, and it is guaranteed to have [the snapshot isolation level](https://en.wikipedia.org/wiki/Snapshot_isolation) for reads. The changes in a transaction are always isolated from other transactions and become visible only after the transaction has been -committed, i.e. the request to the GraphQL API has been processed and was successful. If a GraphQL API request results in +committed, i.e. the request to the GraphQL API has been processed and was successful. If a GraphQL API request results in any kind of error, the transaction is automatically rolled back. @@ -358,9 +358,9 @@ any kind of error, the transaction is automatically rolled back. -Parallel transaction handling hasn't been finalized yet, and is scheduled to be finalized in -[issue #16](https://github.com/FgForrest/evitaDB/issues/16). Until this issue is resolved, you must ensure that only -a single client is writing to the catalog in parallel. Other clients may have been reading in parallel using read-only +Parallel transaction handling hasn't been finalized yet, and is scheduled to be finalized in +[issue #16](https://github.com/FgForrest/evitaDB/issues/16). Until this issue is resolved, you must ensure that only +a single client is writing to the catalog in parallel. Other clients may have been reading in parallel using read-only sessions, but the writer must be only one. @@ -386,7 +386,7 @@ evitaDB recognizes two types of sessions:
read-only
Read-only sessions are opened by calling GraphQL queries, i.e. `getCollectionName`, `listCollectionName`, - `queryCollectionName` and so on. No write operations are allowed in a read-only session + `queryCollectionName` and so on. No write operations are allowed in a read-only session which allows evitaDB to optimize its behavior when working with the database.
read-write
Read-write sessions are opened by calling GraphQL mutations, i.e. `upsertCollectionName`, `deleteCollectionName` @@ -409,7 +409,7 @@ evitaDB recognizes two types of sessions: -In the future, the read-only sessions can be distributed to multiple read nodes, while the read-write sessions must +In the future, the read-only sessions can be distributed to multiple read nodes, while the read-write sessions must talk to the master node. @@ -418,49 +418,49 @@ talk to the master node. #### Unsafe session lifecycle -We recommend to open sessions using `queryCatalog` / `updateCatalog` methods that accept a lambda function to execute -your business logic. This way evitaDB can safely handle the lifecycle management of *sessions* & *transactions*. -This approach is not always acceptable - for example, if your application needs to be integrated into an existing -framework that only provides a lifecycle callback methods, there is no way to "wrap" the entire business logic in +We recommend to open sessions using `queryCatalog` / `updateCatalog` methods that accept a lambda function to execute +your business logic. This way evitaDB can safely handle the lifecycle management of *sessions* & *transactions*. +This approach is not always acceptable - for example, if your application needs to be integrated into an existing +framework that only provides a lifecycle callback methods, there is no way to "wrap" the entire business logic in the lambda function. That's why there is an alternative - not so secure - approach to handling sessions and transactions: - + [Manual session and transaction handling](/documentation/user/en/use/api/example/manual-transaction-management.java) -If you use manual *session / transaction* handling, you must ensure that for every scope opening there is +If you use manual *session / transaction* handling, you must ensure that for every scope opening there is a corresponding closing (even if an exception occurs during your business logic call). -Both evita_api/src/main/java/io/evitadb/api/EvitaSessionContract.java and -evita_api/src/main/java/io/evitadb/api/TransactionContract.java implement Java +Both evita_api/src/main/java/io/evitadb/api/EvitaSessionContract.java and +evita_api/src/main/java/io/evitadb/api/TransactionContract.java implement Java `Autocloseable` interface, so you can use them this way: - + [Get advantage of Autocloseable behaviour](/documentation/user/en/use/api/example/autocloseable-transaction-management.java) -This approach is safe, but has the same disadvantage as using `queryCatalog` / `updateCatalog` methods - you need to +This approach is safe, but has the same disadvantage as using `queryCatalog` / `updateCatalog` methods - you need to have all the business logic executable within the same block. #### Dry-run session For testing purposes, there is a special flag that can be used when opening a new session - a **dry run** flag: - + [Opening dry-run session](/documentation/user/en/use/api/example/dry-run-session.java) In this session, all transactions will automatically have a *rollback* flag set when they are opened, without the need to set the rollback flag manually. This fact greatly simplifies -[transaction rollback on test teardown pattern](http://xunitpatterns.com/Transaction%20Rollback%20Teardown.html) when -implementing your tests, or can be useful if you want to ensure that the changes are not committed in a particular +[transaction rollback on test teardown pattern](http://xunitpatterns.com/Transaction%20Rollback%20Teardown.html) when +implementing your tests, or can be useful if you want to ensure that the changes are not committed in a particular session, and you don't have easy access to the places where the transaction is opened. @@ -476,13 +476,13 @@ a delegate function. That's why there is an alternative - not so secure - approach to handling sessions and transactions: - + [Manual session and transaction handling](/documentation/user/en/use/api/example/manual-transaction-management.cs) -If you use manual *session / transaction* handling, you must ensure that for every scope opening there is +If you use manual *session / transaction* handling, you must ensure that for every scope opening there is a corresponding closing (even if an exception occurs during your business logic call). @@ -490,7 +490,7 @@ Both EvitaDB.Client/EvitaClientSession.cs and EvitaDB.Client/EvitaClientTransaction.cs implement C# `IDisposable` interface, so you can use them this way: - + [Get advantage of Autocloseable behaviour](/documentation/user/en/use/api/example/autocloseable-transaction-management.cs) @@ -502,7 +502,7 @@ have all the business logic executable within the same block. For testing purposes, there is a special flag that can be used when opening a new session - a **dry run** flag: - + [Opening dry-run session](/documentation/user/en/use/api/example/dry-run-session.cs) @@ -529,7 +529,7 @@ Anyway, there is also the [possibility of creating them directly](#creating-enti Usually the entity creation will look like this: - + [Creating new entity example](/documentation/user/en/use/api/example/create-new-entity.java) @@ -538,35 +538,35 @@ This way, the created entity can be immediately checked against the schema. This and it may be split into several parts, which will reveal the "builder" used in the process. When you need to alter existing entity, you first fetch it from the server, open for writing (which converts it to -the builder wrapper), modify it, and finally collect the changes and send them to the server. +the builder wrapper), modify it, and finally collect the changes and send them to the server. - + [Updating existing entity example](/documentation/user/en/use/api/example/update-existing-entity.java) The `upsertVia` method is a shortcut for calling `session.upsertEntity(builder.buildChangeSet())`. If you look at the -evita_api/src/main/java/io/evitadb/api/requestResponse/data/BuilderContract.java you'll +evita_api/src/main/java/io/evitadb/api/requestResponse/data/BuilderContract.java you'll see, that you can call on it either:
`buildChangeSet`
Creates a stream of *mutations* that represent the changes you've made to the immutable object.
`toInstance`
-
Creates a new version of the immutable entity object with all changes applied. This allows you to create a - new instance of the object locally without sending the changes to the server. When you fetch the same instance +
Creates a new version of the immutable entity object with all changes applied. This allows you to create a + new instance of the object locally without sending the changes to the server. When you fetch the same instance from the server again, you'll see that none of the changes have been applied to the database entity.
-
+
evita_api/src/main/java/io/evitadb/api/requestResponse/data/mutation/EntityMutation.java or evita_api/src/main/java/io/evitadb/api/requestResponse/data/BuilderContract.java can be passed to evita_api/src/main/java/io/evitadb/api/EvitaSessionContract.java `upsert` method, -which returns +which returns evita_api/src/main/java/io/evitadb/api/requestResponse/data/structure/EntityReference.java containing only entity type and (possibly assigned) primary key information. You can also use the `upsertAndFetchEntity` -method, which inserts or creates the entity and returns its body in the form and size you specify in your `require` +method, which inserts or creates the entity and returns its body in the form and size you specify in your `require` argument. #### Custom contracts @@ -581,7 +581,7 @@ Entity instances can be created even if no EvitaDB instance is available: [Detached instantiation example](/documentation/user/en/use/api/example/detached-instantiation.java) -Although you will probably only take advantage of this approach when writing test cases, it still allows you to create +Although you will probably only take advantage of this approach when writing test cases, it still allows you to create a stream of mutations that can be sent to and processed by the evitaDB server once you manage to get its instance. There is an analogous builder that takes an existing entity and tracks changes made to it. @@ -600,7 +600,7 @@ Anyway, there is also the [possibility of creating them directly](#creating-enti Usually the entity creation will look like this: - + [Creating new entity example](/documentation/user/en/use/api/example/create-new-entity.cs) @@ -611,7 +611,7 @@ and it may be split into several parts, which will reveal the "builder" used in When you need to alter existing entity, you first fetch it from the server, open for writing (which converts it to the builder wrapper), modify it, and finally collect the changes and send them to the server. - + [Updating existing entity example](/documentation/user/en/use/api/example/update-existing-entity.cs) @@ -624,10 +624,10 @@ The `UpsertVia` method is a shortcut for calling `session.UpsertEntity(builder.B
`BuildChangeSet`
Creates a stream of *mutations* that represent the changes you've made to the immutable object.
`ToInstance`
-
Creates a new version of the immutable entity object with all changes applied. This allows you to create a - new instance of the object locally without sending the changes to the server. When you fetch the same instance +
Creates a new version of the immutable entity object with all changes applied. This allows you to create a + new instance of the object locally without sending the changes to the server. When you fetch the same instance from the server again, you'll see that none of the changes have been applied to the database entity.
-
+
EvitaDB.Client/Models/Data/Mutations/IEntityMutation.cs or @@ -659,8 +659,8 @@ There is an analogous builder that takes an existing entity and tracks changes m
-In the GraphQL API, there is no way to send full entity object to the server to be stored. Instead, you send a collection -of mutations that add, change, or remove individual data from an entity (new or existing one). Similarly to how the schema +In the GraphQL API, there is no way to send full entity object to the server to be stored. Instead, you send a collection +of mutations that add, change, or remove individual data from an entity (new or existing one). Similarly to how the schema is defined in the GraphQL API. @@ -685,7 +685,7 @@ at the `https://your-server:5555/gql/test-catalog` URL. This API contains `upser the changes to be applied to an entity. In one go, you can then retrieve the entity with the changes applied by defining return data. - + [Creating new entity example](/documentation/user/en/use/api/example/create-new-entity.graphql) @@ -695,7 +695,7 @@ either create a new entity with specified mutations (and possibly a primary key) of an existing entity is specified. You can further customize the behavior of the mutation by specifying the `entityExistence` argument. - + [Updating existing entity example](/documentation/user/en/use/api/example/update-existing-entity.graphql) @@ -723,12 +723,12 @@ the entity definition (see Java API for inspiration). You can create a new entity or update an existing one using the [catalog API](/documentation/user/en/use/connectors/rest.md#rest-api-instances) -at a collection endpoint, for example `https://your-server:5555/test/test-catalog/product` with `PUT` HTTP method. -There endpoints are customized to collections' [schemas](/documentation/user/en/use/schema.md#entity). These endpoints take a -collection of evitaDB mutations which define the changes to be applied to an entity. In one go, you can then retrieve the +at a collection endpoint, for example `https://your-server:5555/test/test-catalog/product` with `PUT` HTTP method. +There endpoints are customized to collections' [schemas](/documentation/user/en/use/schema.md#entity). These endpoints take a +collection of evitaDB mutations which define the changes to be applied to an entity. In one go, you can then retrieve the entity with the changes applied by defining requirements. - + [Creating new entity example](/documentation/user/en/use/api/example/create-new-entity.rest) @@ -738,7 +738,7 @@ either create a new entity with specified mutations (and possibly a primary key) of an existing entity is specified. You can further customize the behavior of the mutation by specifying the `entityExistence` argument. - + [Updating existing entity example](/documentation/user/en/use/api/example/update-existing-entity.rest) @@ -765,7 +765,7 @@ To remove one or multiple entities, you need to define a query that will match a - + [Removing all entities which name starts with `A`](/documentation/user/en/use/api/example/delete-entities-by-query.java) @@ -786,12 +786,12 @@ entities in usual way. -evitaDB may not remove all entities matched by the filter part of the query. The removal of entities is subject to the -logic of the pagination arguments `offset` and `limit`. Even if you omit the -these arguments completely, implicit pagination (`offset: 1, limit: 20`) will be used. If the number of entities removed is equal to the +evitaDB may not remove all entities matched by the filter part of the query. The removal of entities is subject to the +logic of the pagination arguments `offset` and `limit`. Even if you omit the +these arguments completely, implicit pagination (`offset: 1, limit: 20`) will be used. If the number of entities removed is equal to the size of the defined paging, you should repeat the removal command. -Massive entity removal is better to execute in multiple transactional rounds rather than in one big transaction, i.e. multiple +Massive entity removal is better to execute in multiple transactional rounds rather than in one big transaction, i.e. multiple GraphQL requests. This is at least a good practice, because large and long-running transactions increase probability of conflicts that lead to rollbacks of other transactions. @@ -809,9 +809,9 @@ entities in usual way. -evitaDB may not remove all entities matched by the filter part of the query. The removal of entities is subject to the -logic of the `require` conditions [`page` or `strip`](../../query/requirements/paging.md). Even if you omit the -`require` part completely, implicit pagination (`page(1, 20)`) will be used. If the number of entities removed is equal to the +evitaDB may not remove all entities matched by the filter part of the query. The removal of entities is subject to the +logic of the `require` conditions [`page` or `strip`](../../query/requirements/paging.md). Even if you omit the +`require` part completely, implicit pagination (`page(1, 20)`) will be used. If the number of entities removed is equal to the size of the defined paging, you should repeat the removal command. Massive entity removal is better to execute in multiple transactional rounds rather than in one big transaction. This is @@ -843,14 +843,14 @@ If you remove only the root node without removing its children, the children wil ##### How does evitaDB handle the removals internally? -No data is actually removed once it is created and stored. If you remove the reference/attribute/whatever, it remains +No data is actually removed once it is created and stored. If you remove the reference/attribute/whatever, it remains in the entity and is just marked as `dropped`. See the evita_api/src/main/java/io/evitadb/api/requestResponse/data/Droppable.javaEvitaDB.Client/Models/Data/IDroppable.cs interface implementations. There are a few reasons for this decision: -1. it's good to have the last known version of the data around when things go wrong, so we can still recover to the +1. it's good to have the last known version of the data around when things go wrong, so we can still recover to the previous state. 2. it allows us to track the changes in the entity through its lifecycle for debugging purposes 3. it is consistent with our *append-only* storage approach where we need to write [tombstones](https://en.wikipedia.org/wiki/Tombstone_(data_store)) in case of entity or other object removals diff --git a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/CatalogGraphQLBuilder.java b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/CatalogGraphQLBuilder.java index 36105f063..f6e937b07 100644 --- a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/CatalogGraphQLBuilder.java +++ b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/CatalogGraphQLBuilder.java @@ -26,7 +26,7 @@ import graphql.GraphQL; import graphql.schema.GraphQLSchema; import io.evitadb.api.CatalogContract; -import io.evitadb.api.EvitaContract; +import io.evitadb.core.Evita; import io.evitadb.externalApi.graphql.api.GraphQLBuilder; import io.evitadb.externalApi.graphql.configuration.GraphQLConfig; import io.evitadb.externalApi.graphql.exception.EvitaDataFetcherExceptionHandler; @@ -43,7 +43,7 @@ public class CatalogGraphQLBuilder implements GraphQLBuilder { @Nonnull - private final EvitaContract evita; + private final Evita evita; @Nonnull private final CatalogContract catalog; @Nonnull diff --git a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/EvitaSessionManagingInstrumentation.java b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/EvitaSessionManagingInstrumentation.java index 4a6cbb80d..fcadc309a 100644 --- a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/EvitaSessionManagingInstrumentation.java +++ b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/EvitaSessionManagingInstrumentation.java @@ -30,8 +30,11 @@ import graphql.execution.instrumentation.SimplePerformantInstrumentation; import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters; import graphql.language.OperationDefinition; -import io.evitadb.api.EvitaContract; +import io.evitadb.api.CatalogContract; +import io.evitadb.api.CatalogState; import io.evitadb.api.EvitaSessionContract; +import io.evitadb.core.Evita; +import io.evitadb.externalApi.graphql.exception.GraphQLInternalError; import io.evitadb.externalApi.graphql.exception.GraphQLSchemaBuildingError; import lombok.RequiredArgsConstructor; @@ -53,7 +56,7 @@ public class EvitaSessionManagingInstrumentation extends SimplePerformantInstrumentation { @Nonnull - private final EvitaContract evita; + private final Evita evita; @Nonnull private final String catalogName; @@ -69,9 +72,13 @@ public ExecutionContext instrumentExecutionContext(@Nonnull ExecutionContext exe evitaSession = evita.createReadOnlySession(catalogName); } else if (operation == OperationDefinition.Operation.MUTATION) { evitaSession = evita.createReadWriteSession(catalogName); - evitaSession.openTransaction(); + final CatalogContract catalog = evita.getCatalogInstance(catalogName) + .orElseThrow(() -> new GraphQLInternalError("Catalog `" + catalogName + "` could not be found.")); + if (catalog.getCatalogState().equals(CatalogState.ALIVE)) { + evitaSession.openTransaction(); + } } else { - throw new GraphQLSchemaBuildingError("Operation `" + operation + "` is currently not supported by evitaDB GraphQL API."); + throw new GraphQLInternalError("Operation `" + operation + "` is currently not supported by evitaDB GraphQL API."); } executionContext.getGraphQLContext().put(GraphQLContextKey.EVITA_SESSION, evitaSession); @@ -86,6 +93,7 @@ public CompletableFuture instrumentExecutionResult(@Nonnull Exe final EvitaSessionContract evitaSession = parameters.getGraphQLContext().get(GraphQLContextKey.EVITA_SESSION); if (evitaSession != null) { // there may not be any session if there was some error in GraphQL query parsing before the session creation + // or the catalog is in WARMING_UP state if (evitaSession.isTransactionOpen()) { evitaSession.closeTransaction(); } diff --git a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/resolver/mutatingDataFetcher/DeleteEntitiesMutatingDataFetcher.java b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/resolver/mutatingDataFetcher/DeleteEntitiesMutatingDataFetcher.java index 2071827e0..ad0f16fb1 100644 --- a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/resolver/mutatingDataFetcher/DeleteEntitiesMutatingDataFetcher.java +++ b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/resolver/mutatingDataFetcher/DeleteEntitiesMutatingDataFetcher.java @@ -60,6 +60,7 @@ import static io.evitadb.api.query.Query.query; import static io.evitadb.api.query.QueryConstraints.collection; +import static io.evitadb.api.query.QueryConstraints.entityFetch; import static io.evitadb.api.query.QueryConstraints.require; import static io.evitadb.api.query.QueryConstraints.strip; @@ -159,7 +160,7 @@ private Require buildRequire(@Nonnull DataFetchingEnvironment environment, extractDesiredLocale(filterBy), entitySchema ); - entityFetch.ifPresent(requireConstraints::add); + entityFetch.ifPresentOrElse(requireConstraints::add, () -> requireConstraints.add(entityFetch())); if (arguments.offset() != null && arguments.limit() != null) { requireConstraints.add(strip(arguments.offset(), arguments.limit())); 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 961c5ed89..2e0980c58 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 @@ -533,9 +533,8 @@ private List createTests( if (ofNullable(sourceCodeTabsMatcher.group(2)).map(it -> it.contains("ignoreTest")).orElse(false)) { continue; } - final String localParameter = sourceCodeTabsMatcher.group(4); - final Environment environment = localParameter != null && "local".equals(localParameter.trim()) ? - Environment.LOCALHOST : profile; + final boolean isLocal = sourceCodeTabsMatcher.group(4) != null && "local".equals(sourceCodeTabsMatcher.group(4).trim()); + final Environment environment = isLocal ? Environment.LOCALHOST : profile; if (!NOT_TESTED_LANGUAGES.contains(referencedFileExtension)) { final Path[] requiredScripts = ofNullable(sourceCodeTabsMatcher.group(2)) diff --git a/evita_functional_tests/src/test/java/io/evitadb/documentation/graphql/GraphQLExecutable.java b/evita_functional_tests/src/test/java/io/evitadb/documentation/graphql/GraphQLExecutable.java index a0e172867..71e5eb19b 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/documentation/graphql/GraphQLExecutable.java +++ b/evita_functional_tests/src/test/java/io/evitadb/documentation/graphql/GraphQLExecutable.java @@ -48,6 +48,7 @@ import java.util.List; import java.util.Optional; import java.util.function.Supplier; +import java.util.regex.Pattern; import static io.evitadb.documentation.UserDocumentationTest.readFile; import static io.evitadb.documentation.UserDocumentationTest.resolveSiblingWithDifferentExtension; @@ -68,6 +69,16 @@ */ @RequiredArgsConstructor public class GraphQLExecutable extends JsonExecutable implements Executable, EvitaTestSupport { + + /** + * Pattern of keywords for identify system instance. + */ + private static final Pattern SYSTEM_INSTANCE_KEYWORDS_PATTERN = Pattern.compile("(createCatalog|switchCatalogToAliveState)"); + /** + * Pattern of keywords for identify schema instance. + */ + private static final Pattern SCHEMA_INSTANCE_KEYWORDS_PATTERN = Pattern.compile("(update[a-zA-Z]+Schema\\()"); + /** * Provides access to the {@link GraphQLTestContext} instance. */ @@ -201,10 +212,20 @@ private static JsonNode extractValueFrom(@Nonnull JsonNode theObject, @Nonnull S @Override public void execute() throws Throwable { final String theQuery = sourceContent; + + final String instancePath; + if (SYSTEM_INSTANCE_KEYWORDS_PATTERN.matcher(theQuery).find()) { + instancePath = "/gql/system"; + } else if (SCHEMA_INSTANCE_KEYWORDS_PATTERN.matcher(theQuery).find()) { + instancePath = "/gql/evita/schema"; + } else { + instancePath = "/gql/evita"; + } + final GraphQLClient graphQLClient = testContextAccessor.get().getGraphQLClient(); final JsonNode theResult; try { - theResult = graphQLClient.call(theQuery); + theResult = graphQLClient.call(instancePath, theQuery); } catch (Exception ex) { fail("The query " + theQuery + " failed: " + ex.getMessage(), ex); return; diff --git a/evita_functional_tests/src/test/java/io/evitadb/documentation/graphql/GraphQLTestContext.java b/evita_functional_tests/src/test/java/io/evitadb/documentation/graphql/GraphQLTestContext.java index 7227b2286..5c5641cde 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/documentation/graphql/GraphQLTestContext.java +++ b/evita_functional_tests/src/test/java/io/evitadb/documentation/graphql/GraphQLTestContext.java @@ -45,7 +45,7 @@ public class GraphQLTestContext implements TestContext { public GraphQLTestContext(@Nonnull Environment profile) { this.graphQLClient = profile == Environment.LOCALHOST ? - new GraphQLClient("https://localhost:5555/gql/evita", false) : - new GraphQLClient("https://demo.evitadb.io:5555/gql/evita"); + new GraphQLClient("https://localhost:5555", false) : + new GraphQLClient("https://demo.evitadb.io:5555"); } } diff --git a/evita_test_support/src/main/java/io/evitadb/test/client/GraphQLClient.java b/evita_test_support/src/main/java/io/evitadb/test/client/GraphQLClient.java index 817473ee0..d6bbba57c 100644 --- a/evita_test_support/src/main/java/io/evitadb/test/client/GraphQLClient.java +++ b/evita_test_support/src/main/java/io/evitadb/test/client/GraphQLClient.java @@ -50,22 +50,27 @@ public GraphQLClient(@Nonnull String url, boolean validateSsl) { } @Nonnull - public JsonNode call(@Nonnull String document) { + public JsonNode call(@Nonnull String instancePath, @Nonnull String document) { HttpURLConnection connection = null; try { - connection = createConnection(); + connection = createConnection(instancePath); writeRequestBody(connection, document); connection.connect(); - Assert.isPremiseValid( - connection.getResponseCode() == 200, - "Call to GraphQL server ended with status " + connection.getResponseCode() + ", query was:\n" + document - ); + final int responseCode = connection.getResponseCode(); + if (responseCode == 200) { + final JsonNode responseBody = readResponseBody(connection.getInputStream()); + validateResponseBody(responseBody); - final JsonNode responseBody = readResponseBody(connection.getInputStream()); - validateResponseBody(responseBody); + return responseBody; + } + if (responseCode >= 400 && responseCode <= 499 && responseCode != 404) { + final JsonNode errorResponse = readResponseBody(connection.getErrorStream()); + final String errorResponseString = objectMapper.writeValueAsString(errorResponse); + throw new EvitaInternalError("Call to GraphQL instance `" + this.url + instancePath + "` ended with status " + responseCode + ", query was:\n" + document + "\n and response was: \n" + errorResponseString); + } - return responseBody; + throw new EvitaInternalError("Call to GraphQL server ended with status " + responseCode + ", query was:\n" + document); } catch (IOException e) { throw new EvitaInternalError("Unexpected error.", e); } finally { @@ -76,8 +81,8 @@ public JsonNode call(@Nonnull String document) { } @Nonnull - private HttpURLConnection createConnection() throws IOException { - final URL url = new URL(this.url); + private HttpURLConnection createConnection(@Nonnull String instancePath) throws IOException { + final URL url = new URL(this.url + instancePath); final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("POST"); connection.setRequestProperty("Content-Type", "application/json"); From 46b4fd88dfa2200f343827e744f991f38ade6b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Mon, 11 Dec 2023 16:26:17 +0100 Subject: [PATCH 19/52] docs(#200): fix GQL/REST write several examples --- documentation/user/en/query/basics.md | 8 ++++---- .../user/en/use/api/example/create-new-entity.graphql | 4 ++-- documentation/user/en/use/api/write-data.md | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/documentation/user/en/query/basics.md b/documentation/user/en/query/basics.md index ff515390e..45fc285c6 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: `collection`, `filterBy`, `orderBy`, `require`): - + [Example random order of query parts](/documentation/user/en/query/examples/randomOrderQuery.evitaql) diff --git a/documentation/user/en/use/api/example/create-new-entity.graphql b/documentation/user/en/use/api/example/create-new-entity.graphql index 0ad5203af..3e77c2e5e 100644 --- a/documentation/user/en/use/api/example/create-new-entity.graphql +++ b/documentation/user/en/use/api/example/create-new-entity.graphql @@ -18,7 +18,7 @@ mutation { }, { upsertAttributeMutation: { - name: "catalogName" + name: "catalogCode" value: "X1605EA-MB044W" } }, @@ -91,7 +91,7 @@ mutation { associatedData { gallery } - prices(priceList: "basic") { + prices(priceLists: "basic") { priceId currency priceWithoutTax diff --git a/documentation/user/en/use/api/write-data.md b/documentation/user/en/use/api/write-data.md index 677ad2123..f5594652d 100644 --- a/documentation/user/en/use/api/write-data.md +++ b/documentation/user/en/use/api/write-data.md @@ -529,7 +529,7 @@ Anyway, there is also the [possibility of creating them directly](#creating-enti Usually the entity creation will look like this: - + [Creating new entity example](/documentation/user/en/use/api/example/create-new-entity.java) @@ -600,7 +600,7 @@ Anyway, there is also the [possibility of creating them directly](#creating-enti Usually the entity creation will look like this: - + [Creating new entity example](/documentation/user/en/use/api/example/create-new-entity.cs) @@ -685,7 +685,7 @@ at the `https://your-server:5555/gql/test-catalog` URL. This API contains `upser the changes to be applied to an entity. In one go, you can then retrieve the entity with the changes applied by defining return data. - + [Creating new entity example](/documentation/user/en/use/api/example/create-new-entity.graphql) @@ -728,7 +728,7 @@ There endpoints are customized to collections' [schemas](/documentation/user/en/ collection of evitaDB mutations which define the changes to be applied to an entity. In one go, you can then retrieve the entity with the changes applied by defining requirements. - + [Creating new entity example](/documentation/user/en/use/api/example/create-new-entity.rest) From 052eff744ceeb1c58216969fbd362316b7dc755b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Novotn=C3=BD?= Date: Mon, 11 Dec 2023 16:49:46 +0100 Subject: [PATCH 20/52] docs: finalized write API documentation --- ...s.java => associated-data-read-class.java} | 0 ...va => associated-data-read-interface.java} | 0 ....java => associated-data-read-record.java} | 0 .../example/associated-data-write-class.java | 38 ++++ .../associated-data-write-interface.java | 50 +++++ ...e-class.java => attribute-read-class.java} | 0 ...ace.java => attribute-read-interface.java} | 0 ...record.java => attribute-read-record.java} | 0 .../api/example/attribute-write-class.java | 29 +++ .../example/attribute-write-interface.java | 68 +++++++ ...rent-class.java => parent-read-class.java} | 2 +- ...erface.java => parent-read-interface.java} | 4 +- ...nt-record.java => parent-read-record.java} | 2 +- .../use/api/example/parent-write-class.java | 21 ++ .../api/example/parent-write-interface.java | 66 +++++++ ...price-class.java => price-read-class.java} | 0 ...terface.java => price-read-interface.java} | 0 ...ice-record.java => price-read-record.java} | 0 .../en/use/api/example/price-write-class.java | 34 ++++ .../api/example/price-write-interface.java | 183 ++++++++++++++++++ .../api/example/primary-key-interface.java | 13 -- ...class.java => primary-key-read-class.java} | 0 .../example/primary-key-read-interface.java | 13 ++ ...cord.java => primary-key-read-record.java} | 0 .../api/example/primary-key-write-class.java | 14 ++ .../example/primary-key-write-interface.java | 22 +++ ...e-class.java => reference-read-class.java} | 8 +- ...ace.java => reference-read-interface.java} | 0 ...record.java => reference-read-record.java} | 0 .../api/example/reference-write-class.java | 124 ++++++++++++ .../example/reference-write-interface.java | 180 +++++++++++++++++ documentation/user/en/use/api/query-data.md | 22 +-- documentation/user/en/use/api/write-data.md | 181 ++++++++++++++++- documentation/user/en/use/connectors/java.md | 2 +- .../data/annotation/RemoveWhenExists.java | 2 +- .../java/io/evitadb/index/CatalogIndex.java | 2 +- .../index/attribute/AttributeIndex.java | 2 +- .../documentation/UserDocumentationTest.java | 20 +- .../META-INF/documentation/imports.java | 2 + 39 files changed, 1061 insertions(+), 43 deletions(-) rename documentation/user/en/use/api/example/{associated-data-class.java => associated-data-read-class.java} (100%) rename documentation/user/en/use/api/example/{associated-data-interface.java => associated-data-read-interface.java} (100%) rename documentation/user/en/use/api/example/{associated-data-record.java => associated-data-read-record.java} (100%) create mode 100644 documentation/user/en/use/api/example/associated-data-write-class.java create mode 100644 documentation/user/en/use/api/example/associated-data-write-interface.java rename documentation/user/en/use/api/example/{attribute-class.java => attribute-read-class.java} (100%) rename documentation/user/en/use/api/example/{attribute-interface.java => attribute-read-interface.java} (100%) rename documentation/user/en/use/api/example/{attribute-record.java => attribute-read-record.java} (100%) create mode 100644 documentation/user/en/use/api/example/attribute-write-class.java create mode 100644 documentation/user/en/use/api/example/attribute-write-interface.java rename documentation/user/en/use/api/example/{parent-class.java => parent-read-class.java} (97%) rename documentation/user/en/use/api/example/{parent-interface.java => parent-read-interface.java} (94%) rename documentation/user/en/use/api/example/{parent-record.java => parent-read-record.java} (96%) create mode 100644 documentation/user/en/use/api/example/parent-write-class.java create mode 100644 documentation/user/en/use/api/example/parent-write-interface.java rename documentation/user/en/use/api/example/{price-class.java => price-read-class.java} (100%) rename documentation/user/en/use/api/example/{price-interface.java => price-read-interface.java} (100%) rename documentation/user/en/use/api/example/{price-record.java => price-read-record.java} (100%) create mode 100644 documentation/user/en/use/api/example/price-write-class.java create mode 100644 documentation/user/en/use/api/example/price-write-interface.java delete mode 100644 documentation/user/en/use/api/example/primary-key-interface.java rename documentation/user/en/use/api/example/{primary-key-class.java => primary-key-read-class.java} (100%) create mode 100644 documentation/user/en/use/api/example/primary-key-read-interface.java rename documentation/user/en/use/api/example/{primary-key-record.java => primary-key-read-record.java} (100%) create mode 100644 documentation/user/en/use/api/example/primary-key-write-class.java create mode 100644 documentation/user/en/use/api/example/primary-key-write-interface.java rename documentation/user/en/use/api/example/{reference-class.java => reference-read-class.java} (93%) rename documentation/user/en/use/api/example/{reference-interface.java => reference-read-interface.java} (100%) rename documentation/user/en/use/api/example/{reference-record.java => reference-read-record.java} (100%) create mode 100644 documentation/user/en/use/api/example/reference-write-class.java create mode 100644 documentation/user/en/use/api/example/reference-write-interface.java diff --git a/documentation/user/en/use/api/example/associated-data-class.java b/documentation/user/en/use/api/example/associated-data-read-class.java similarity index 100% rename from documentation/user/en/use/api/example/associated-data-class.java rename to documentation/user/en/use/api/example/associated-data-read-class.java diff --git a/documentation/user/en/use/api/example/associated-data-interface.java b/documentation/user/en/use/api/example/associated-data-read-interface.java similarity index 100% rename from documentation/user/en/use/api/example/associated-data-interface.java rename to documentation/user/en/use/api/example/associated-data-read-interface.java diff --git a/documentation/user/en/use/api/example/associated-data-record.java b/documentation/user/en/use/api/example/associated-data-read-record.java similarity index 100% rename from documentation/user/en/use/api/example/associated-data-record.java rename to documentation/user/en/use/api/example/associated-data-read-record.java diff --git a/documentation/user/en/use/api/example/associated-data-write-class.java b/documentation/user/en/use/api/example/associated-data-write-class.java new file mode 100644 index 000000000..abb034fc2 --- /dev/null +++ b/documentation/user/en/use/api/example/associated-data-write-class.java @@ -0,0 +1,38 @@ +@EntityRef("Product") +@Data +public class MyEntityEditor { + + // example ComplexDataObject type + public record Localization( + @Nonnull Map texts + ) { } + + // contains associated data `warrantySpecification` if fetched and not null + @AssociatedData(name = "warrantySpecification", localized = true) + @Nullable private String warrantySpecification; + + // contains associated data `warrantySpecification` if fetched and not null + @AssociatedDataRef("warrantySpecification") + @Nullable private String warrantySpecificationAgain; + + // contains associated data `parameters` or null if not fetched or not set + @AssociatedDataRef("parameters") + @Nullable private String[] parameters; + + // contains associated data `parameters` or empty collection if not fetched or not set (it never contains null value) + @AssociatedDataRef("parameters") + @Nonnull private Collection parametersAsCollection; + + // contains associated data `parameters` or empty list if not fetched or not set (it never contains null value) + @AssociatedDataRef("parameters") + @Nonnull private List parametersAsList; + + // contains associated data `parameters` or empty set if not fetched or not set (it never contains null value) + @AssociatedDataRef("parameters") + @Nonnull private Set parametersAsSet; + + // contains associated data `localization` or null if not fetched or not set + @AssociatedDataRef("localization") + @Nullable private Localization localization; + +} \ No newline at end of file diff --git a/documentation/user/en/use/api/example/associated-data-write-interface.java b/documentation/user/en/use/api/example/associated-data-write-interface.java new file mode 100644 index 000000000..25dd3ee3a --- /dev/null +++ b/documentation/user/en/use/api/example/associated-data-write-interface.java @@ -0,0 +1,50 @@ +@EntityRef("Product") +public interface MyEntityEditor extends MyEntity { + + // sets localized associatedData `warrantySpecification` + // annotation is not specified and will be automatically resolved from the corresponding getter method + // if the NULL value is set - associatedData will be removed from the instance + void setWarrantySpecification(@Nullable String warrantySpecification, @Nonnull Locale locale); + + // sets localized associated data `warrantySpecification` in specified locale using explicit associatedData pairing + // returns reference to self instance to allow builder pattern chaining + // if the NULL value is set - associatedData will be removed from the instance + @AssociatedDataRef("warrantySpecification") + @Nonnull + MyEntityEditor setWarrantySpecificationAgain(@Nullable String warrantySpecification, @Nonnull Locale locale); + + // sets associatedData `parameters` using explicit associatedData pairing + // returns reference to self instance to allow builder pattern chaining + // if the NULL value is set - associatedData will be removed from the instance + @AssociatedDataRef("parameters") + @Nonnull + MyEntityEditor setParameters(@Nullable String[] parameters); + + // sets associatedData `parameters` using explicit associatedData pairing + // returns reference to self instance to allow builder pattern chaining + // if the NULL or empty collection is set - associated data will be removed from the instance + // alternatives accepting `List` or `Set` parameters are also available + @AssociatedDataRef("parameters") + @Nonnull + MyEntityEditor setParametersAsCollection(@Nullable Collection parameters); + + // sets localized associatedData `localization` + // annotation is not specified and will be automatically resolved from the corresponding getter method + // if the NULL value is set - associatedData will be removed from the instance + void setLocalization(@Nullable Localization localization, @Nonnull Locale locale); + + // removes attribute `name` of particular locale from the entity + // returns reference to self instance to allow builder pattern chaining + // alternative for calling `setName(null, locale)` + @RemoveWhenExists + @AttributeRef("warrantySpecification") + @Nonnull + MyEntityEditor removeWarrantySpecification(@Nonnull Locale locale); + + // removes attribute `markets` from the entity and returns the removed value as the result + // alternatives returning `List` or `Set` or array types are also available + @AttributeRef("parameters") + @Nonnull + Collection removeParameters(); + +} \ No newline at end of file diff --git a/documentation/user/en/use/api/example/attribute-class.java b/documentation/user/en/use/api/example/attribute-read-class.java similarity index 100% rename from documentation/user/en/use/api/example/attribute-class.java rename to documentation/user/en/use/api/example/attribute-read-class.java diff --git a/documentation/user/en/use/api/example/attribute-interface.java b/documentation/user/en/use/api/example/attribute-read-interface.java similarity index 100% rename from documentation/user/en/use/api/example/attribute-interface.java rename to documentation/user/en/use/api/example/attribute-read-interface.java diff --git a/documentation/user/en/use/api/example/attribute-record.java b/documentation/user/en/use/api/example/attribute-read-record.java similarity index 100% rename from documentation/user/en/use/api/example/attribute-record.java rename to documentation/user/en/use/api/example/attribute-read-record.java diff --git a/documentation/user/en/use/api/example/attribute-write-class.java b/documentation/user/en/use/api/example/attribute-write-class.java new file mode 100644 index 000000000..8a561e44c --- /dev/null +++ b/documentation/user/en/use/api/example/attribute-write-class.java @@ -0,0 +1,29 @@ +@EntityRef("Product") +@Data +public class MyEntityEditor { + + // contains attribute `name` if fetched and not null + @Attribute(name = "name", localized = true) + @Nullable private String name; + + // contains attribute `name` if fetched and not null + @AttributeRef("name") + @Nullable private String nameAgain; + + // contains attribute `markets` or null if not fetched or not set + @AttributeRef("markets") + @Nullable private String[] markets; + + // contains attribute `markets` or empty collection if not fetched or not set (it never contains null value) + @AttributeRef("markets") + @Nonnull private Collection marketsAsCollection; + + // contains attribute `markets` or empty list if not fetched or not set (it never contains null value) + @AttributeRef("markets") + @Nonnull private List marketsAsList; + + // contains attribute `markets` or empty set if not fetched or not set (it never contains null value) + @AttributeRef("markets") + @Nonnull private Set marketsAsSet; + +} \ No newline at end of file diff --git a/documentation/user/en/use/api/example/attribute-write-interface.java b/documentation/user/en/use/api/example/attribute-write-interface.java new file mode 100644 index 000000000..810f33850 --- /dev/null +++ b/documentation/user/en/use/api/example/attribute-write-interface.java @@ -0,0 +1,68 @@ +@EntityRef("Product") +public interface MyEntityEditor extends MyEntity { + + // sets localized attribute `name` in specified locale + // annotation is not specified and will be automatically resolved from the corresponding getter method + // if the NULL value is set - attribute will be removed from the instance + void setName(@Nullable String name, @Nonnull Locale locale); + + // sets localized attribute `name` in specified locale using explicit attribute pairing + // returns reference to self instance to allow builder pattern chaining + // if the NULL value is set - attribute will be removed from the instance + @AttributeRef("name") + @Nonnull + MyEntityEditor setNameAgain(@Nullable String name, @Nonnull Locale locale); + + // sets attribute `code` + // annotation is not specified and will be automatically resolved from the corresponding getter method + // if the NULL value is set - attribute will be removed from the instance + void setCode(@Nullable String code); + + // sets attribute `code` using explicit attribute pairing + // returns reference to self instance to allow builder pattern chaining + // if the NULL value is set - attribute will be removed from the instance + @AttributeRef("name") + @Nonnull + MyEntityEditor setCodeAgain(@Nullable String code); + + // sets attribute `markets` using explicit attribute pairing + // returns reference to self instance to allow builder pattern chaining + // if the NULL value is set - attribute will be removed from the instance + @AttributeRef("markets") + @Nonnull + MyEntityEditor setMarkets(@Nullable String[] markets); + + // sets attribute `markets` using explicit attribute pairing + // returns reference to self instance to allow builder pattern chaining + // if the NULL or empty collection is set - attribute will be removed from the instance + // alternatives accepting `List` or `Set` parameters are also available + @AttributeRef("markets") + @Nonnull + MyEntityEditor setMarketsAsCollection(@Nullable Collection markets); + + // removes attribute `name` of particular locale from the entity + // returns reference to self instance to allow builder pattern chaining + // alternative for calling `setName(null, locale)` + @RemoveWhenExists + @AttributeRef("name") + @Nonnull + MyEntityEditor removeName(@Nonnull Locale locale); + + // removes attribute `code` from the entity + // alternative for calling `setCode(null)` + @RemoveWhenExists + @AttributeRef("code") + void removeCode(); + + // removes attribute `code` from the entity and returns the removed value as the result + @RemoveWhenExists + @AttributeRef("code") + String removeCodeAndReturnIt(); + + // removes attribute `markets` from the entity and returns the removed value as the result + // alternatives returning `List` or `Set` or array types are also available + @AttributeRef("markets") + @Nonnull + Collection removeMarkets(); + +} \ No newline at end of file diff --git a/documentation/user/en/use/api/example/parent-class.java b/documentation/user/en/use/api/example/parent-read-class.java similarity index 97% rename from documentation/user/en/use/api/example/parent-class.java rename to documentation/user/en/use/api/example/parent-read-class.java index 6da32caed..a000fa772 100644 --- a/documentation/user/en/use/api/example/parent-class.java +++ b/documentation/user/en/use/api/example/parent-read-class.java @@ -1,4 +1,4 @@ -@EntityRef("Product") +@EntityRef("Category") @Data public class MyEntity { diff --git a/documentation/user/en/use/api/example/parent-interface.java b/documentation/user/en/use/api/example/parent-read-interface.java similarity index 94% rename from documentation/user/en/use/api/example/parent-interface.java rename to documentation/user/en/use/api/example/parent-read-interface.java index e132281a0..1476e36b1 100644 --- a/documentation/user/en/use/api/example/parent-interface.java +++ b/documentation/user/en/use/api/example/parent-read-interface.java @@ -1,4 +1,4 @@ -@EntityRef("Product") +@EntityRef("Category") public interface MyEntity { // return id of parent entity, or null if this entity is a root entity @@ -26,7 +26,7 @@ public interface MyEntity { // return optional reference to a parent entity, or empty if this entity is a root entity @ParentEntity - @Nonnull Optional getParentEntityReferenceIfPresent(); + @Nonnull Optional getParentEntityReferenceIfPresent(); // return reference to a parent wrapped in this interface, or null if this entity is a root entity // method throws ContextMissingException if the information about parent was not fetched from the server diff --git a/documentation/user/en/use/api/example/parent-record.java b/documentation/user/en/use/api/example/parent-read-record.java similarity index 96% rename from documentation/user/en/use/api/example/parent-record.java rename to documentation/user/en/use/api/example/parent-read-record.java index c323b0240..4a6dc0f15 100644 --- a/documentation/user/en/use/api/example/parent-record.java +++ b/documentation/user/en/use/api/example/parent-read-record.java @@ -1,4 +1,4 @@ -@EntityRef("Product") +@EntityRef("Category") public record MyEntity( // contains id of parent entity, or null if this entity is a root entity @ParentEntity diff --git a/documentation/user/en/use/api/example/parent-write-class.java b/documentation/user/en/use/api/example/parent-write-class.java new file mode 100644 index 000000000..054d98616 --- /dev/null +++ b/documentation/user/en/use/api/example/parent-write-class.java @@ -0,0 +1,21 @@ +@EntityRef("Category") +@Data +public class MyEntityEditor { + + // contains id of parent entity; or null if this entity is a root entity + @ParentEntity + @Nullable private Integer parentId; + + // contains parent entity; or null if this entity is a root entity + @ParentEntity + @Nullable private SealedEntity parentEntity; + + // contains reference to a parent entity; or null if this entity is a root entity + @ParentEntity + @Nullable private EntityReferenceContract parentEntityReference; + + // contains reference to a parent wrapped in this interface; or null if this entity is a root entity + @ParentEntity + @Nullable private MyEntityEditor parentMyEntity; + +} \ No newline at end of file diff --git a/documentation/user/en/use/api/example/parent-write-interface.java b/documentation/user/en/use/api/example/parent-write-interface.java new file mode 100644 index 000000000..b09ac3a4f --- /dev/null +++ b/documentation/user/en/use/api/example/parent-write-interface.java @@ -0,0 +1,66 @@ +@EntityRef("Product") +public interface MyEntityEditor extends MyEntity { + + // sets entity with passed primary key as parent of this entity + // if null is passed, parent is removed and this entity becomes one of the root entities + // returns reference to self instance to allow builder pattern chaining + @ParentEntity + @Nonnull MyEntityEditor setParentId(@Nullable Integer parentId); + + // sets passed entity of the same type as parent of this entity + // if null is passed, parent is removed and this entity becomes one of the root entities + @ParentEntity + void setParentEntity(@Nullable MyEntity parentEntity); + + // sets entity using reference DTO as parent of this entity + // if null is passed, parent is removed and this entity becomes one of the root entities + // returns reference to self instance to allow builder pattern chaining + @ParentEntity + @Nonnull MyEntityEditor setParentEntityReference(@Nullable EntityReferenceContract parentEntityReference); + + // sets entity using `EntityClassifier` interface as parent of this entity + // if null is passed, parent is removed and this entity becomes one of the root entities + @ParentEntity + void setParentEntityClassifier(@Nullable EntityClassifier parentEntityClassifier); + + // sets entity using `EntityClassifierWithParent` interface as parent of this entity + // if null is passed, parent is removed and this entity becomes one of the root entities + @ParentEntity + void setParentEntityClassifierWithParent(@Nullable EntityClassifierWithParent parentEntityClassifierWithParent); + + // sets entity with passed primary key as parent of this entity and allows to update data of this parent entity + // using passed `Consumer` interface implementation + // if entity of particular primary key does not exist, it is created with the data set by passed `Consumer` + // interface implementation + // method returns reference to self instance to allow builder pattern chaining + @ParentEntity + @Nonnull MyEntityEditor withParent(int parentPrimaryKey, @Nonnull Consumer setupLogic); + + // method removes parent entity of this entity + // if this entity does not have parent, method does nothing + // current entity becomes one of the root entities when the changes are persisted + @ParentEntity + @RemoveWhenExists + void removeParent(); + + // method removes parent entity of this entity and returns true if parent entity was removed + // if this entity does not have parent, method does nothing and returns false + // current entity becomes one of the root entities when the changes are persisted + @ParentEntity + @RemoveWhenExists + boolean removeParentAndReturnResult(); + + // method removes parent entity of this entity and returns its primary key if parent entity was removed + // if this entity does not have parent, method does nothing and returns null + @ParentEntity + @RemoveWhenExists + @Nullable Integer removeParentAndReturnItsPrimaryKey(); + + // method removes parent entity of this entity and returns its instance if parent entity was removed + // if this entity does not have parent, method does nothing and returns null + // method throws exception if the parent entity body was not fetched from the database + @ParentEntity + @RemoveWhenExists + @Nullable MyEntity removeParentAndReturnIt() throws ContextMissingException; + +} \ No newline at end of file diff --git a/documentation/user/en/use/api/example/price-class.java b/documentation/user/en/use/api/example/price-read-class.java similarity index 100% rename from documentation/user/en/use/api/example/price-class.java rename to documentation/user/en/use/api/example/price-read-class.java diff --git a/documentation/user/en/use/api/example/price-interface.java b/documentation/user/en/use/api/example/price-read-interface.java similarity index 100% rename from documentation/user/en/use/api/example/price-interface.java rename to documentation/user/en/use/api/example/price-read-interface.java diff --git a/documentation/user/en/use/api/example/price-record.java b/documentation/user/en/use/api/example/price-read-record.java similarity index 100% rename from documentation/user/en/use/api/example/price-record.java rename to documentation/user/en/use/api/example/price-read-record.java diff --git a/documentation/user/en/use/api/example/price-write-class.java b/documentation/user/en/use/api/example/price-write-class.java new file mode 100644 index 000000000..abd91d1e9 --- /dev/null +++ b/documentation/user/en/use/api/example/price-write-class.java @@ -0,0 +1,34 @@ +@EntityRef("Product") +@Data +public class MyEntityEditor { + + // contains the prices calculated as selling price for particular query + @PriceForSale + @Nullable private PriceContract priceForSale; + + // contains all the prices that compete for being the selling price for particular entity + // from all those prices only the first one is used as selling price + @PriceForSaleRef + @Nullable private PriceContract[] allPricesAvailableForSale; + + // contains price from the price list with name `basic` if it was fetched from the server + @Price(priceList = "basic") + @Nullable private PriceContract basicPrice; + + // contains all prices of the entity that were fetched from the server + @Price + @Nonnull private Collection allPrices; + + // contains all prices of the entity that were fetched from the server as list + @Price + @Nonnull private List allPricesAsList; + + // contains all prices of the entity that were fetched from the server as set + @Price + @Nonnull private Set allPricesAsSet; + + // contains all prices of the entity that were fetched from the server as array + @Price + @Nullable private PriceContract[] allPricesAsArray; + +} \ No newline at end of file diff --git a/documentation/user/en/use/api/example/price-write-interface.java b/documentation/user/en/use/api/example/price-write-interface.java new file mode 100644 index 000000000..47e4e6276 --- /dev/null +++ b/documentation/user/en/use/api/example/price-write-interface.java @@ -0,0 +1,183 @@ +@EntityRef("Product") +public interface MyEntityEditor extends MyEntity { + + // creates or updates the price of the product in price list "basic" + // the updated price is found by combination of priceId, priceList and currency + // all necessary data are provided in the parameter value + // if the price in parameter relates to different price list than "basic", exception is thrown + // returns reference to self instance to allow builder pattern chaining + @Price(priceList = "basic") + @Nonnull MyEntityEditor setBasicPrice(@Nonnull PriceContract basicPrice); + + // analogous to the previous method, but the price is provided as a set of parameters + @Price(priceList = "basic") + void setBasicPrice( + @Nonnull BigDecimal priceWithoutTax, + @Nonnull BigDecimal priceWithTax, + @Nonnull BigDecimal taxRate, + @Nonnull String currencyCode, + int priceId, + @Nullable DateTimeRange validIn, + @Nullable Integer innerRecordId + ); + + // analogous to the previous method, but it lacks optional parameters + // also the currency is provided as a Currency object and not as a String + // returns reference to self instance to allow builder pattern chaining + @Price(priceList = "basic") + @Nonnull MyEntityEditor setBasicPrice( + @Nonnull BigDecimal priceWithoutTax, + @Nonnull BigDecimal priceWithTax, + @Nonnull BigDecimal taxRate, + @Nonnull Currency currency, + int priceId + ); + + // analogous to the previous method, but it accepts price list name as a parameter instead of annotation + // returns reference to self instance to allow builder pattern chaining + @Price + @Nonnull MyEntityEditor setPrice( + @Nonnull BigDecimal priceWithoutTax, + @Nonnull BigDecimal priceWithTax, + @Nonnull BigDecimal taxRate, + @Nonnull String priceList, + @Nonnull Currency currencyCode, + int priceId, + @Nullable DateTimeRange validIn, + @Nullable Integer innerRecordId + ); + + // creates or updates the price of the product with price list defined in the parameter body + // the updated price is found by combination of priceId, priceList and currency + @Price + void setPrice(@Nonnull PriceContract price); + + // rewrites all prices of the product + // returns reference to self instance to allow builder pattern chaining + @Price + @Nonnull MyEntityEditor setAllPricesAsList(@Nonnull List allPricesAsList); + + // rewrites all prices of the product using var-arg parameters + @Price + void setAllPricesAsArray(PriceContract... allPricesAsArray); + + // removes all prices of the product with matching external price id + // returns reference to self instance to allow builder pattern chaining + @Price + @RemoveWhenExists + @Nonnull MyEntityEditor removePricesById(int priceId); + + // removes price with matching combination of priceId, priceList and currency + // returns the removed price (if any was found and removed) + @Price + @RemoveWhenExists + @Nullable PriceContract removePriceByIdAndReturnIt(int priceId, @Nonnull String priceList, @Nonnull Currency currency); + + // removes price with matching combination of priceId, priceList and currency + // returns the removed price external id (if any was found and removed) + @Price + @RemoveWhenExists + @Nullable Integer removePriceByIdAndReturnItsId(int priceId, @Nonnull String priceList, @Nonnull Currency currency); + + // removes price with matching combination of priceId, priceList and currency + // returns the removed price business key wrapped in a record (if any was found and removed) + @Price + @RemoveWhenExists + @Nullable PriceKey removePriceByIdAndReturnItsPriceKey(int priceId, @Nonnull String priceList, @Nonnull Currency currency); + + // removes price with matching combination of priceId, priceList and currency + // returns true if the removed price was found and removed + @Price + @RemoveWhenExists + boolean removePriceByIdAndReturnTrueIfRemoved(int priceId, @Nonnull String priceList, @Nonnull Currency currency); + + // removes all prices of the product with matching price list name + // returns reference to self instance to allow builder pattern chaining + @Price + @RemoveWhenExists + @Nonnull MyEntityEditor removePricesByPriceList(@Nonnull String priceList); + + // removes all prices of the product with matching price list name + // returns list of all removed prices or empty collection + @Price + @RemoveWhenExists + @Nonnull Collection removePricesByPriceListAndReturnTheirCollection(@Nonnull String priceList); + + // removes all prices of the product with matching price list name + // returns list of all removed price external ids or empty collection + @Price + @RemoveWhenExists + Collection removePricesByPriceListAndReturnTheirIds(@Nonnull String priceList); + + // removes all prices of the product with matching price list name + // returns list of all removed price business keys wrapped in a record or empty collection + @Price + @RemoveWhenExists + Collection removePricesByPriceListAndReturnTheirKeys(@Nonnull String priceList); + + // removes all prices of the product with matching currency + @Price + @RemoveWhenExists + void removePricesByCurrency(@Nonnull Currency currency); + + // removes all prices of the product with matching currency + // returns array of all removed prices or empty array (you can also use Collection instead of array) + @Price + @RemoveWhenExists + @Nonnull PriceContract[] removePricesByCurrencyAndReturnTheirArray(@Nonnull Currency currency); + + // removes all prices of the product with matching currency + // returns array of all removed price external ids or empty array (you can also use Collection instead of array) + @Price + @RemoveWhenExists + @Nonnull int[] removePricesByCurrencyAndReturnArrayOfTheirIds(@Nonnull Currency currency); + + // removes all prices of the product with matching currency + // returns array of all removed price business keys or empty array (you can also use Collection instead of array) + @Price + @RemoveWhenExists + @Nonnull PriceKey[] removePricesByCurrencyAndReturnArrayOfTheirPriceKeys(@Nonnull Currency currency); + + // removes price by combination of priceId, priceList and currency + // returns reference to self instance to allow builder pattern chaining + @Price + @RemoveWhenExists + @Nonnull MyEntityEditor removePrice(int priceId, @Nonnull String priceList, @Nonnull Currency currency); + + // removes price by combination of priceId and currency in price list "basic" + // returns reference to self instance to allow builder pattern chaining + @Price(priceList = "basic") + @RemoveWhenExists + @Nonnull MyEntityEditor removeBasicPrice(int priceId, @Nonnull Currency currency); + + // removes price by combination of priceId, priceList and currency + // returns true if the price was found and removed + @Price + @RemoveWhenExists + boolean removePrice(@Nonnull PriceContract price); + + // removes all prices of the entity + // returns true if at least one price was found and removed + @Price + @RemoveWhenExists + boolean removeAllPrices(); + + // removes all prices of the entity + // returns array of all removed price external ids or empty array (you can also use Collection instead of array) + @Price + @RemoveWhenExists + @Nonnull int[] removeAllPricesAndReturnTheirIds(); + + // removes all prices of the entity + // returns array of all removed price business keys or empty array (you can also use Collection instead of array) + @Price + @RemoveWhenExists + @Nonnull PriceKey[] removeAllPricesAndReturnTheirPriceKeys(); + + // removes all prices of the entity + // returns array of all removed prices or empty array (you can also use Collection instead of array) + @Price + @RemoveWhenExists + @Nonnull PriceContract[] removeAllPricesAndReturnThem(); + +} \ No newline at end of file diff --git a/documentation/user/en/use/api/example/primary-key-interface.java b/documentation/user/en/use/api/example/primary-key-interface.java deleted file mode 100644 index 70907b392..000000000 --- a/documentation/user/en/use/api/example/primary-key-interface.java +++ /dev/null @@ -1,13 +0,0 @@ -@EntityRef("Product") -public interface MyEntity { - - @PrimaryKey int id(); - @PrimaryKey Integer idAsIntegerObject(); - @PrimaryKey long idAsLong(); - @PrimaryKey Long idAsLongObject(); - @PrimaryKeyRef int idAlternative(); - @PrimaryKeyRef Integer idAlternativeAsIntegerObject(); - @PrimaryKeyRef long idAlternativeAsLong(); - @PrimaryKeyRef Long idAlternativeAsLongObject(); - -} \ No newline at end of file diff --git a/documentation/user/en/use/api/example/primary-key-class.java b/documentation/user/en/use/api/example/primary-key-read-class.java similarity index 100% rename from documentation/user/en/use/api/example/primary-key-class.java rename to documentation/user/en/use/api/example/primary-key-read-class.java diff --git a/documentation/user/en/use/api/example/primary-key-read-interface.java b/documentation/user/en/use/api/example/primary-key-read-interface.java new file mode 100644 index 000000000..13798a772 --- /dev/null +++ b/documentation/user/en/use/api/example/primary-key-read-interface.java @@ -0,0 +1,13 @@ +@EntityRef("Product") +public interface MyEntity { + + @PrimaryKey int getId(); + @PrimaryKey Integer getIdAsIntegerObject(); + @PrimaryKey long getIdAsLong(); + @PrimaryKey Long getIdAsLongObject(); + @PrimaryKeyRef int getIdAlternative(); + @PrimaryKeyRef Integer getIdAlternativeAsIntegerObject(); + @PrimaryKeyRef long getIdAlternativeAsLong(); + @PrimaryKeyRef Long getIdAlternativeAsLongObject(); + +} \ No newline at end of file diff --git a/documentation/user/en/use/api/example/primary-key-record.java b/documentation/user/en/use/api/example/primary-key-read-record.java similarity index 100% rename from documentation/user/en/use/api/example/primary-key-record.java rename to documentation/user/en/use/api/example/primary-key-read-record.java diff --git a/documentation/user/en/use/api/example/primary-key-write-class.java b/documentation/user/en/use/api/example/primary-key-write-class.java new file mode 100644 index 000000000..d2049c71a --- /dev/null +++ b/documentation/user/en/use/api/example/primary-key-write-class.java @@ -0,0 +1,14 @@ +@EntityRef("Product") +@Data +public class MyEntityEditor { + @PrimaryKey private int id; + @PrimaryKey private Integer idAsIntegerObject; + @PrimaryKey private long idAsLong; + @PrimaryKey private Long idAsLongObject; + + @PrimaryKeyRef private int idRef; + @PrimaryKeyRef private Integer idRefAsIntegerObject; + @PrimaryKeyRef private long idRefAsLong; + @PrimaryKeyRef private Long idRefAsLongObject; + +} \ No newline at end of file diff --git a/documentation/user/en/use/api/example/primary-key-write-interface.java b/documentation/user/en/use/api/example/primary-key-write-interface.java new file mode 100644 index 000000000..6f0eb8751 --- /dev/null +++ b/documentation/user/en/use/api/example/primary-key-write-interface.java @@ -0,0 +1,22 @@ +@EntityRef("Product") +public interface MyEntityEditor extends MyEntity { + + @PrimaryKey void setId(int id); + @PrimaryKey void setIdAsIntegerObject(@Nonnull Integer id); + @PrimaryKey void setIdAsLong(long id); + @PrimaryKey void setIdAsLongObject(@Nonnull Long id); + @PrimaryKeyRef void setIdAlternative(int id); + @PrimaryKeyRef void setIdAlternativeAsIntegerObject(@Nonnull Integer id); + @PrimaryKeyRef void setIdAlternativeAsLong(long id); + @PrimaryKeyRef void setIdAlternativeAsLongObject(@Nonnull Long id); + + @PrimaryKey @Nonnull MyEntityEditor setIdReturningSelf(int id); + @PrimaryKey @Nonnull MyEntityEditor setIdAsIntegerObjectReturningSelf(@Nonnull Integer id); + @PrimaryKey @Nonnull MyEntityEditor setIdAsLongReturningSelf(long id); + @PrimaryKey @Nonnull MyEntityEditor setIdAsLongObjectReturningSelf(@Nonnull Long id); + @PrimaryKeyRef @Nonnull MyEntityEditor setIdAlternativeReturningSelf(int id); + @PrimaryKeyRef @Nonnull MyEntityEditor setIdAlternativeAsIntegerObjectReturningSelf(@Nonnull Integer id); + @PrimaryKeyRef @Nonnull MyEntityEditor setIdAlternativeAsLongReturningSelf(long id); + @PrimaryKeyRef @Nonnull MyEntityEditor setIdAlternativeAsLongObjectReturningSelf(@Nonnull Long id); + +} \ No newline at end of file diff --git a/documentation/user/en/use/api/example/reference-class.java b/documentation/user/en/use/api/example/reference-read-class.java similarity index 93% rename from documentation/user/en/use/api/example/reference-class.java rename to documentation/user/en/use/api/example/reference-read-class.java index a40544075..c28e32929 100644 --- a/documentation/user/en/use/api/example/reference-class.java +++ b/documentation/user/en/use/api/example/reference-read-class.java @@ -2,18 +2,18 @@ @Data public class MyEntity { - // component contains referenced entity Brand if such reference with cardinality ZERO_OR_ONE exists + // field contains referenced entity Brand if such reference with cardinality ZERO_OR_ONE exists // and the Brand entity is fetched along with MyEntity // throws ContextMissingException if the reference information was not fetched from the server @ReferenceRef("brand") @Nullable private final Brand brand; - // component contains referenced Brand entity primary key if such reference with cardinality ZERO_OR_ONE exists + // field contains referenced Brand entity primary key if such reference with cardinality ZERO_OR_ONE exists // throws ContextMissingException if the reference information was not fetched from the server @ReferenceRef("brand") @Nullable private final Integer brandId; - // component contains collection of referenced ProductParameter references or empty list + // field contains collection of referenced ProductParameter references or empty list // reference `parameters` has cardinality ZERO_OR_MORE // throws ContextMissingException if the reference information was not fetched from the server @ReferenceRef("parameters") @@ -31,7 +31,7 @@ public class MyEntity { @ReferenceRef("parameters") @NonNull private final ProductParameter[] parameterAsArray; - // component contains array of referenced Parameter entities or null + // field contains array of referenced Parameter entities or null // reference `parameters` has cardinality ZERO_OR_MORE // throws ContextMissingException if the reference information was not fetched from the server @ReferenceRef("parameters") diff --git a/documentation/user/en/use/api/example/reference-interface.java b/documentation/user/en/use/api/example/reference-read-interface.java similarity index 100% rename from documentation/user/en/use/api/example/reference-interface.java rename to documentation/user/en/use/api/example/reference-read-interface.java diff --git a/documentation/user/en/use/api/example/reference-record.java b/documentation/user/en/use/api/example/reference-read-record.java similarity index 100% rename from documentation/user/en/use/api/example/reference-record.java rename to documentation/user/en/use/api/example/reference-read-record.java diff --git a/documentation/user/en/use/api/example/reference-write-class.java b/documentation/user/en/use/api/example/reference-write-class.java new file mode 100644 index 000000000..852806a21 --- /dev/null +++ b/documentation/user/en/use/api/example/reference-write-class.java @@ -0,0 +1,124 @@ +@EntityRef("Product") +@Data +public class MyEntityEditor { + + // field contains referenced entity Brand if such reference with cardinality ZERO_OR_ONE exists + // and the Brand entity is fetched along with MyEntity + // throws ContextMissingException if the reference information was not fetched from the server + @ReferenceRef("brand") + @Nullable private Brand brand; + + // field contains referenced Brand entity primary key if such reference with cardinality ZERO_OR_ONE exists + // throws ContextMissingException if the reference information was not fetched from the server + @ReferenceRef("brand") + @Nullable private Integer brandId; + + // field contains collection of referenced ProductParameter references or empty list + // reference `parameters` has cardinality ZERO_OR_MORE + // throws ContextMissingException if the reference information was not fetched from the server + @ReferenceRef("parameters") + @NonNull private List parameters; + + // alternative format for `Parameters` method with similar behaviour + @ReferenceRef("parameters") + @NonNull private Set parametersAsSet; + + // alternative format for `Parameters` method with similar behaviour + @ReferenceRef("parameters") + @NonNull private Collection parameterAsCollection; + + // alternative format for `Parameters` method with similar behaviour + @ReferenceRef("parameters") + @NonNull private ProductParameter[] parameterAsArray; + + // field contains array of referenced Parameter entities or null + // reference `parameters` has cardinality ZERO_OR_MORE + // throws ContextMissingException if the reference information was not fetched from the server + @ReferenceRef("parameters") + @Nullable private int[] parameterIds; + + // simplified Brand entity interface + // this example demonstrates the option to return directly referenced entities from the main entity + @EntityRef("Brand") + @Data + public class Brand implements Serializable { + @PrimaryKeyRef + private int id; + + // attribute code of the Brand entity + @AttributeRef("code") + @Nullable private String code; + + } + + // simplified Parameter entity interface + @EntityRef("Parameter") + @Data + public class Parameter implements Serializable { + + @PrimaryKeyRef + private int id; + + // attribute code of the Parameter entity + @AttributeRef("code") + @Nullable private String code; + + } + + // simplified ParameterGroup entity interface + @EntityRef("ParameterGroup") + @Data + public class ParameterGroup implements Serializable { + + @PrimaryKeyRef + private int id; + + // attribute code of the ParameterGroup entity + @AttributeRef("code") + @Nullable private String code; + + } + + // simplified ProductParameter entity interface + // this example demonstrates the option to return interfaces covering the references that provide further access + // to the referenced entities (both grouping and referenced entities) + @EntityRef("Parameter") + @Data + public class ProductParameter implements Serializable { + + @ReferencedEntity + private int primaryKey; + + // attribute code of the reference to the Parameter entity + @AttributeRef("priority") + @Nullable private Long priority; + + // primary key of the referenced entity of the reference + @ReferencedEntity + @Nullable private Integer parameter; + + // reference to the referenced entity descriptor of the reference + @ReferencedEntity + @Nullable private EntityReferenceContract parameterEntityClassifier; + + // reference to the referenced entity of the reference + // throws ContextMissingException if the referenced entity was not fetched from the server + @ReferencedEntity + @Nullable private Parameter parameterEntity; + + // primary key of the grouping entity of the reference to the Parameter entity + @ReferencedEntityGroup + @Nullable private Integer parameterGroup; + + // reference to the grouping entity descriptor of the reference to the Parameter entity + @ReferencedEntityGroup + @Nullable private EntityReferenceContract parameterGroupEntityClassifier; + + // reference to the grouping entity of the reference to the Parameter entity + // throws ContextMissingException if the referenced entity was not fetched from the server + @ReferencedEntityGroup + @Nullable private ParameterGroup parameterGroupEntity; + + } + +} \ No newline at end of file diff --git a/documentation/user/en/use/api/example/reference-write-interface.java b/documentation/user/en/use/api/example/reference-write-interface.java new file mode 100644 index 000000000..299f9468e --- /dev/null +++ b/documentation/user/en/use/api/example/reference-write-interface.java @@ -0,0 +1,180 @@ +@EntityRef("Product") +public interface MyEntityEditor extends MyEntity { + + // simplified Brand entity editor interface + // this example demonstrates the option to create referenced entities directly from the main entity + public interface BrandEditor extends Brand { + + // setter for Brand `code` attribute + // annotation is not specified and will be automatically resolved from the corresponding getter method + @Nonnull BrandEditor setCode(@Nonnull String code); + + } + + // simplified Parameter entity editor interface + // this example demonstrates the option to create referenced entities directly from the main entity reference + public interface ParameterEditor extends Parameter { + + // setter for Parameter `code` attribute + // annotation is not specified and will be automatically resolved from the corresponding getter method + void setCode(String code); + + } + + // simplified ParameterGroup entity editor interface + // this example demonstrates the option to create referenced entities directly from the main entity reference + public interface ParameterGroupEditor extends ParameterGroup { + + // setter for ParameterGroup `code` attribute + // annotation is not specified and will be automatically resolved from the corresponding getter method + void setCode(String code); + + } + + // simplified product parameter reference entity editor interface + // this example demonstrates the option to create referenced entities directly from the main entity + public interface ProductParameterEditor extends ProductParameter { + + // setter for reference attribute + // annotation is not specified and will be automatically resolved from the corresponding getter method + // returns reference to self instance to allow builder pattern chaining + @Nonnull ProductParameterEditor setPriority(@Nullable Long priority); + + // returns editor for the Parameter entity referenced by this reference + // throws ContextMissingException if the referenced entity was not fetched from the database + // changes in the referenced entity can be persisted separately or using upsertDeeply on product entity + @ReferencedEntity + @Nonnull ParameterEditor getParameterForUpdate() throws ContextMissingException; + + // removes group of the reference to ParameterGroup entity if it exists + @ReferencedEntityGroup + @RemoveWhenExists + void removeParameterGroup(); + + // sets group of the reference to ParameterGroup entity with passed primary key + // if null is passed, group information on this reference is removed + void setParameterGroup(@Nullable Integer parameterGroup); + + // sets group of the reference to ParameterGroup entity using passed `EntityReferenceContract` + // if null is passed, group information on this reference is removed + void setParameterGroupEntityClassifier(@Nullable EntityReferenceContract entityClassifier); + + // sets group of the reference to ParameterGroup entity using passed ParameterGroup entity + // if null is passed, group information on this reference is removed + void setParameterGroupEntity(@Nullable ParameterGroup groupEntity); + + // returns editor for new (non-existing) ParameterGroup entity and uses its primary key as group of the reference + // passed Consumer implementation can setup the new ParameterGroup entity + // changes in the referenced entity can be persisted separately or using upsertDeeply on product entity + // returns reference to self instance to allow builder pattern chaining + @ReferencedEntityGroup + @CreateWhenMissing + @Nonnull ProductParameterEditor getOrCreateParameterGroupEntity(@Nonnull Consumer groupEntity); + + } + + // sets ZERO_OR_ONE brand reference to entity with passed primary key + // if null is passed, reference is removed + // annotation is not specified and will be automatically resolved from the corresponding getter method + // returns reference to self instance to allow builder pattern chaining + @Nonnull MyEntityEditor setBrand(@Nullable Integer brandId); + + // sets ZERO_OR_ONE brand reference to passed entity + // if null is passed, reference is removed + // annotation is not specified and will be automatically resolved from the corresponding getter method + // returns reference to self instance to allow builder pattern chaining + @Nonnull void setBrand(@Nullable Brand brand); + + // sets ZERO_OR_ONE brand reference to newly created brand entity + // which is setup in the Consumer implementation + // returns reference to self instance to allow builder pattern chaining + @ReferenceRef("brand") + @Nonnull MyEntityEditor setNewBrand(@CreateWhenMissing @Nonnull Consumer brandConsumer); + + // updates existing ZERO_OR_ONE brand reference entity data + // this method cannot be used to remove the reference or create referenced Brand entity - just to update its data + // it's not casual to update referenced entity data, usually this form is used to update the reference itself and + // its attributes + // returns reference to self instance to allow builder pattern chaining + @ReferenceRef("brand") + @Nonnull MyEntityEditor updateBrand(@Nonnull Consumer brandConsumer); + + // creates new or updates existing ZERO_OR_ONE reference to brand entity + // this is the alternative way of creating or updating the reference to the Brand entity + // Brand may or may not exists and if it does not exist, it will be created when the entity is persisted + @ReferenceRef("brand") + @CreateWhenMissing + @Nonnull BrandEditor getOrCreateBrand(); + + // removes existing ZERO_OR_ONE brand reference (the entity itself is not removed) + // returns reference to self instance to allow builder pattern chaining + @ReferenceRef("brand") + @RemoveWhenExists + @Nonnull MyEntityEditor removeBrand(); + + // removes existing ZERO_OR_ONE brand reference (the entity itself is not removed) + // returns the removed entity or NULL if the reference was not set + @ReferenceRef("brand") + @RemoveWhenExists + @Nullable Brand removeBrandAndReturnItsBody(); + + // creates reference to parameter with passed primary key + // if the reference already exists the method does nothing + // returns reference to self instance to allow builder pattern chaining + @ReferenceRef("parameters") + @Nonnull MyEntityEditor addParameter(int parameterId); + + // replaces all existing references to parameters with references to passed primary keys + // if the list is empty, all existing references to parameters are removed + // returns reference to self instance to allow builder pattern chaining + @ReferenceRef("parameters") + @Nonnull MyEntityEditor setParameters(@Nonnull List storeIds); + + // replaces all existing references to parameters with references to passed parameter entities + // if the list is empty, all existing references to parameters are removed + // returns reference to self instance to allow builder pattern chaining + @ReferenceRef("parameters") + @Nonnull MyEntityEditor setParametersAsEntities(@Nonnull List parameters); + + // replaces all existing references to parameters with references to passed parameter primary keys + // if the array is empty, all existing references to parameters are removed + @ReferenceRef("parameters") + @Nonnull void setStoresByPrimaryKeys(int... parameterId); + + // replaces all existing references to parameters with references to passed parameter entities + // if the array is empty, all existing references to parameters are removed + @ReferenceRef("parameters") + @Nonnull void setStores(Parameter... parameter); + + // similar method to `setNewBrand` but works with references with cardinality ZERO_OR_MORE or ONE_OR_MORE + // it tries to find existing reference to parameter with primary key equal to `parameterId` and if such is found + // its passed to `parameterEditor`, if not - new reference is created and passed to `parameterEditor` + @ReferenceRef("parameters") + @Nonnull MyEntityEditor createOrUpdateParameter(int parameterId, @CreateWhenMissing @Nonnull Consumer parameterEditor); + + // alternative to previous method but this doesn't create new reference if it doesn't exist since it's not annotated + // with `@CreateWhenMissing` annotation + @ReferenceRef("parameters") + @Nonnull MyEntityEditor updateParameter(int parameterId, @Nonnull Consumer parameterEditor); + + // removes existing reference to parameter with passed primary key + // returns reference to the removed reference or NULL if the reference was not set + @ReferenceRef("parameters") + @RemoveWhenExists + @Nullable ProductParameter removeParameterAndReturnItsBody(int parameterId); + + // removes all existing references to parameters and returns their primary keys in a list + // returns empty list if there are no references to parameters + // method can also return Collection, Set or array with the same result + @ReferenceRef("parameters") + @RemoveWhenExists + List removeAllProductParametersAndReturnTheirIds(); + + // removes all existing references to parameters and returns removed references in a list + // returns empty list if there are no references to parameters + // method can also return Collection, Set or array with the same result + @ReferenceRef("parameters") + @RemoveWhenExists + List removeAllProductCategoriesAndReturnTheirBodies(); + +} \ No newline at end of file diff --git a/documentation/user/en/use/api/query-data.md b/documentation/user/en/use/api/query-data.md index 7e3a0d702..9e32fcd1d 100644 --- a/documentation/user/en/use/api/query-data.md +++ b/documentation/user/en/use/api/query-data.md @@ -288,23 +288,23 @@ versatile and can also be applied to classes. However, if you follow the record where the data is passed through the constructor, you're limited in some features and other behavior: - You cannot distinguish between the fact that the data was not fetched and the fact that the data does not exist. -- You cannot use controllable accessors that change output based on method parameters (for example, `String getName(Locale, locale)`), +- You cannot use controllable accessors that change output based on method parameters (for example, `String getName(Locale locale)`), because there is no way to represent the method parameters in the constructor arguments. The record and immutable class example variants are suitable for secondary data structures or simplified structures returned as a result of reference getter calls, where you don't need the full-fledged read contract. -Methods annotated with these annotations must follow the expected signature conventions: +Methods annotated with these annotations must follow the expected method signature conventions: #### Primary key In order to access the primary key of the entity, you must use number data type (usually -[int](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html)) and annotate it with the `@PrimaryKey` -or `@PrimaryKeyRef` annotation: +[int](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html)) and annotate it with the evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/PrimaryKey.java +or evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/PrimaryKeyRef.java annotation: -[Example interface with primary key access](/documentation/user/en/use/api/example/primary-key-interface.java) +[Example interface with primary key access](/documentation/user/en/use/api/example/primary-key-read-interface.java) @@ -323,7 +323,7 @@ or [Set](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/ -[Example interface with attribute access](/documentation/user/en/use/api/example/attribute-interface.java) +[Example interface with attribute access](/documentation/user/en/use/api/example/attribute-read-interface.java) @@ -357,11 +357,11 @@ or [Set](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/ -[Example interface with associated data access](/documentation/user/en/use/api/example/associated-data-interface.java) +[Example interface with associated data access](/documentation/user/en/use/api/example/associated-data-read-interface.java) -If the method returns ["non-supported"](../data-types.md#simple-data-types) evitaDB automatically converts the data +If the method returns ["non-supported data type"](../data-types.md#simple-data-types) evitaDB automatically converts the data from ["complex data type"](../data-types.md#complex-data-types) using [documented deserialization rules](../data-types.md#deserialization). #### Prices @@ -381,7 +381,7 @@ If the method can return multiple prices, you need to wrap it in [Collection](ht -[Example interface with prices](/documentation/user/en/use/api/example/price-interface.java) +[Example interface with prices](/documentation/user/en/use/api/example/price-read-interface.java) @@ -401,7 +401,7 @@ or [OptionalLong](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/j -[Example interface with parent access](/documentation/user/en/use/api/example/parent-interface.java) +[Example interface with parent access](/documentation/user/en/use/api/example/parent-read-interface.java) @@ -426,7 +426,7 @@ If the method can return multiple references, you need to wrap it in [Collection -[Example interface with references](/documentation/user/en/use/api/example/reference-interface.java) +[Example interface with references](/documentation/user/en/use/api/example/reference-read-interface.java) diff --git a/documentation/user/en/use/api/write-data.md b/documentation/user/en/use/api/write-data.md index 5a513560f..7dd546d55 100644 --- a/documentation/user/en/use/api/write-data.md +++ b/documentation/user/en/use/api/write-data.md @@ -571,7 +571,186 @@ argument. #### Custom contracts -TODO JNO - DOCUMENT ME +Similar to [query data using custom contracts](query-data.md#custom-contracts), you can also create new entities and +modify existing ones using custom contracts. This allows you to completely bypass working with the evitaDB internal +model and stick to your own - domain specific - model. When modeling your read/write contracts, we recommend to stick +to the [sealed/open principle](../connectors/java.md#data-modeling-recommendations). + +Your write contract will likely extend read contracts using annotations described in +the [schema API](schema-api.md#schema-controlling-annotations) and/or [query data API](query-data.md#custom-contracts). +If you follow the Java Beans naming convention, you don't need to use annotations on write methods, but if you want to +use different names or clarify your write contract, just use the [query data annotations](query-data.md#custom-contracts) +on write methods. In some cases, you may want to use the following additional annotations: + +
+
evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/CreateWhenMissing.java
+
+ Annotation can be used on methods accepting [Consumer](/https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/function/Consumer.html) + type or methods that return/accept your custom contract. When the method is called, the automatic + implementation logic will create a new instance of this contract for you to work with. The new instance is + persisted along with the entity that was responsible for creating it (see details in the following paragraphs). +
+
evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/RemoveWhenExists.java
+
+ Annotation can be used on methods and will trigger the removal of the specific entity data - attribute, + associated data, parent reference, entity reference, or price. The removal only affects the entity itself, + never the target entity. Physical removal is only performed when the entity itself is upserted to the database. +
+
+ +Write methods can return several types (in some cases the supported list is even longer - but these cases are described +in the appropriate sections): + +- `void` - the method performs the modification and returns no value +- `selfType` - the method executes the modification and returns the reference to the contract itself, which allows + to chain multiple write calls together ([builder pattern](https://blogs.oracle.com/javamagazine/post/exploring-joshua-blochs-builder-design-pattern-in-java)) + +In the following sections, we'll describe the behavior of the automatic implementation logic in detail and with +examples: + + + +The examples contain only interface/class definitions since the Java record is read-only. Examples describe +a read/write contract in the same class, which is the simpler approach, but not entirely safe in terms of parallel +access to the data. If you want to follow the recommended [sealed/open principle](../connectors/java.md#data-modeling-recommendations) +you should declare `extends SealedEntity` in the read interface contract +and `extends InstanceEditor` in the write interface contract. + + + + + +When you create new (non-existing) entities using methods annotated with `@CreateWhenMissing`, these entities are held +in local memory and their persistence is delayed until the entity that created them is persisted using +the `upsertDeeply` method. If you don't call this method, or call the simple `upsert` method, the created entities and +references to them will be lost. You may also want to persist them separately or before the main entity that created +them. In this case you can call the `upsert` method on them directly. + +The API allows you to create an infinite depth chain of dependent entities and the `upsertDeeply` / `upsert` logic will +work correctly at all levels. If you create entity `A` in which you created a reference to entity `B` in which you +created another reference to entity `C`, the `upsertDeeply` method called on entity `A` will persist all three entities +in the correct order (`C`, `B`, `A`). If you call the `upsertDeeply` method on entity `B`, it will only persist +the sub-entities in the correct order (`C`, `B`). You can also manually call the `upsert` method on entity `C`, then `B` +and finally `A`. However, if you persist entity `A` without first persisting entities `B` and `C`, the reference between +`A` and `B` will be dropped. You can still call `upsertDeeply` on entity `B`, which will keep the reference between `B` +and `C`. + + + +##### Primary key + +The primary key might be assigned by evitaDB, but can be set also from the outside. To allow setting the primary key you +need to declare method accepting number data type (usually +[int](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html)) and annotate it with the `@PrimaryKey` +or `@PrimaryKeyRef` annotation: + + + +[Example interface with primary key modifier](/documentation/user/en/use/api/example/primary-key-write-interface.java) + + + +##### Attributes + +To set the entity or reference attribute, you must use the appropriate data type and annotate it with the +evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/Attribute.java +or evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/AttributeRef.java +annotation or have a corresponding getter (or field) with this annotation in the same class. + +If the attribute represents a multi-value type (array), you can also wrap it in [Collection](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Collection.html) +(or its specializations [List](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/List.html) +or [Set](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Set.html)) or pass it as simple array of values. The rules apply to both for entity and reference attributes: + + + +[Example interface with attribute modifier](/documentation/user/en/use/api/example/attribute-write-interface.java) + + + + + +Java enum data types are automatically converted to evitaDB string data type using the `name()` method and vice versa +using the `valueOf()` method. + + + +##### Associated Data + +To set the entity associated data, you must use the appropriate data type and annotate it with the +evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/AssociatedData.java +or evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/AssociatedDataRef.java +annotation or have a corresponding getter (or field) with this annotation in the same class. + +If the associated date represents a multi-value type (array), you can also wrap it in [Collection](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Collection.html) +(or its specializations [List](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/List.html) +or [Set](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Set.html)) or pass it as simple array of values. + + + +[Example interface with associated data modifier](/documentation/user/en/use/api/example/associated-data-write-interface.java) + + + +If the method accepts ["non-supported data type"](../data-types.md#simple-data-types) evitaDB automatically converts the data +to ["complex data type"](../data-types.md#complex-data-types) using [documented deserialization rules](../data-types.md#deserialization). + +##### Prices + +To set the entity prices, you could work with +evita_api/src/main/java/io/evitadb/api/requestResponse/data/PriceContract.java data type or +pass all necessary dat in method parameters and annotate the methods with +the evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/Price.java +annotation. You can set (create or update) a single price by its business key, which consists of: + +- **`priceId`** - number datatype with external price identifier +- **`currency`** - [Currency](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Currency.html) or [string](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html) datatype accepting 3-letter currency ISO code +- **`priceList`** - [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html) data type with price list name + +or you can set all prices using the method that takes the array, [Collection](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Collection.html), [List](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/List.html) or +[Set](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Set.html) parameter with all entity prices. + + + +[Example interface with price modifier](/documentation/user/en/use/api/example/price-write-interface.java) + + + +##### Hierarchy + +To set the hierarchy placement information of the entity (i.e., its parent), you must use either the numeric data +type, your own custom interface type, evita_api/src/main/java/io/evitadb/api/requestResponse/data/SealedEntity.java +or evita_api/src/main/java/io/evitadb/api/requestResponse/data/structure/EntityReference.java +data type and annotate it with the evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/ParentEntity.java +annotation or have a corresponding getter (or field) with this annotation in the same class. + + + +[Example interface with parent modifier](/documentation/user/en/use/api/example/parent-write-interface.java) + + + +If you set the value to `NULL`, the entity becomes a root entity. + +##### References + +To set the entity references, you must use either the numeric data type, your own custom interface type, +evita_api/src/main/java/io/evitadb/api/requestResponse/data/EntityReferenceContract.java +or evita_api/src/main/java/io/evitadb/api/requestResponse/data/ReferenceContract.java +data type and annotate it with the evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/Reference.java +or evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/ReferenceRef.java +annotation or have a corresponding getter (or field) with this annotation in the same class. + +If the reference has `ZERO_OR_MORE` or `ONE_OR_MORE` cardinality, you can also wrap it in [Collection](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Collection.html) +(or its specializations [List](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/List.html) +or [Set](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Set.html)), or pass it as a simple array +to rewrite all the values at once. If the reference has `ZERO_OR_ONE` cardinality and you pass a `NULL` value, +the reference is automatically removed. + + + +[Example interface with reference modifier](/documentation/user/en/use/api/example/reference-write-interface.java) + + #### Creating entities in detached mode diff --git a/documentation/user/en/use/connectors/java.md b/documentation/user/en/use/connectors/java.md index 3a1fd343b..84270d575 100644 --- a/documentation/user/en/use/connectors/java.md +++ b/documentation/user/en/use/connectors/java.md @@ -277,7 +277,7 @@ the beginning, it will pay off in the long run. The reasons behind this idea are 1. the read instances remain immutable and can be safely shared between threads and cached in shared memory 2. the read interface is not polluted with methods that are not needed to read data, and stays clean and simple. -We call this the "sealed/open" principle, and it works like this. +We call this the "sealed/open" principle, and it works like this: #### 1. define a read only interface diff --git a/evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/RemoveWhenExists.java b/evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/RemoveWhenExists.java index 726e07c44..e4a7557ec 100644 --- a/evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/RemoveWhenExists.java +++ b/evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/RemoveWhenExists.java @@ -37,7 +37,7 @@ */ @Documented @Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD, ElementType.PARAMETER}) +@Target({ElementType.METHOD}) public @interface RemoveWhenExists { } diff --git a/evita_engine/src/main/java/io/evitadb/index/CatalogIndex.java b/evita_engine/src/main/java/io/evitadb/index/CatalogIndex.java index 00273f300..862fbf418 100644 --- a/evita_engine/src/main/java/io/evitadb/index/CatalogIndex.java +++ b/evita_engine/src/main/java/io/evitadb/index/CatalogIndex.java @@ -179,7 +179,7 @@ public void removeUniqueAttribute( ) { final AttributeKey lookupKey = createAttributeKey(attributeSchema, allowedLocales, locale, value); final GlobalUniqueIndex theUniqueIndex = this.uniqueIndex.get(lookupKey); - notNull(theUniqueIndex, "Unique index for attribute " + attributeSchema.getName() + " not found!"); + notNull(theUniqueIndex, "Unique index for attribute `" + attributeSchema.getName() + "` not found!"); theUniqueIndex.unregisterUniqueKey(value, entitySchema.getName(), locale, recordId); if (theUniqueIndex.isEmpty()) { diff --git a/evita_engine/src/main/java/io/evitadb/index/attribute/AttributeIndex.java b/evita_engine/src/main/java/io/evitadb/index/attribute/AttributeIndex.java index 315e83c3b..b8190c5a2 100644 --- a/evita_engine/src/main/java/io/evitadb/index/attribute/AttributeIndex.java +++ b/evita_engine/src/main/java/io/evitadb/index/attribute/AttributeIndex.java @@ -241,7 +241,7 @@ public void removeUniqueAttribute( ) { final AttributeKey lookupKey = createUniqueAttributeKey(attributeSchema, allowedLocales, locale, value); final UniqueIndex theUniqueIndex = this.uniqueIndex.get(lookupKey); - notNull(theUniqueIndex, "Unique index for attribute " + attributeSchema.getName() + " not found!"); + notNull(theUniqueIndex, "Unique index for attribute `" + attributeSchema.getName() + "` not found!"); theUniqueIndex.unregisterUniqueKey(value, recordId); if (theUniqueIndex.isEmpty()) { 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 c0cdd45ea..95aec55a9 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 @@ -97,7 +97,7 @@ public class UserDocumentationTest implements EvitaTestSupport { * Pattern for searching for blocks. */ private static final Pattern SOURCE_ALTERNATIVE_TABS_PATTERN = Pattern.compile( - "\\s*\\[.*?]\\((.*?)\\)\\s*", + "\\s*\\[.*?]\\((.*?)\\)\\s*
", Pattern.DOTALL | Pattern.MULTILINE ); /** @@ -403,7 +403,7 @@ Stream testDocumentation() throws IOException { Stream testSingleFileDocumentation() { return this.createTests( DocumentationProfile.DEFAULT, - getRootDirectory().resolve("documentation/user/en/use/api/schema-api.md"), + getRootDirectory().resolve("documentation/user/en/use/api/write-data.md"), ExampleFilter.values() ).stream(); } @@ -569,9 +569,17 @@ private List createTests( final Matcher sourceAlternativeTabsMatcher = SOURCE_ALTERNATIVE_TABS_PATTERN.matcher(fileContent); while (sourceAlternativeTabsMatcher.find()) { - final Path referencedFile = createPathRelativeToRootDirectory(rootDirectory, sourceAlternativeTabsMatcher.group(2)); + final Path referencedFile = createPathRelativeToRootDirectory(rootDirectory, sourceAlternativeTabsMatcher.group(4)); final String referencedFileExtension = getFileNameExtension(referencedFile); - final String[] variants = sourceAlternativeTabsMatcher.group(1).split("\\|"); + final Path[] requiredScripts = ofNullable(sourceAlternativeTabsMatcher.group(2)) + .map( + requires -> Arrays.stream(requires.split(",")) + .filter(it -> !it.isBlank()) + .map(it -> createPathRelativeToRootDirectory(rootDirectory, it).normalize()) + .toArray(Path[]::new) + ) + .orElse(null); + final String[] variants = sourceAlternativeTabsMatcher.group(3).split("\\|"); if (!NOT_TESTED_LANGUAGES.contains(referencedFileExtension)) { final List outputSnippet = ofNullable(outputSnippetIndex.get(referencedFile)) .orElse(Collections.emptyList()); @@ -594,7 +602,7 @@ private List createTests( readFileOrThrowException(relatedFile), rootDirectory, relatedFile, - null, + requiredScripts, contextAccessor, codeSnippetIndex, ofNullable( @@ -613,7 +621,7 @@ private List createTests( readFileOrThrowException(referencedFile), rootDirectory, referencedFile, - null, + requiredScripts, contextAccessor, codeSnippetIndex, outputSnippet, diff --git a/evita_functional_tests/src/test/resources/META-INF/documentation/imports.java b/evita_functional_tests/src/test/resources/META-INF/documentation/imports.java index fe35799b5..416a6f782 100644 --- a/evita_functional_tests/src/test/resources/META-INF/documentation/imports.java +++ b/evita_functional_tests/src/test/resources/META-INF/documentation/imports.java @@ -36,6 +36,7 @@ import io.evitadb.api.requestResponse.data.EntityContract; import io.evitadb.api.requestResponse.data.structure.EntityDecorator; import io.evitadb.api.requestResponse.data.EntityClassifier; +import io.evitadb.api.requestResponse.data.EntityClassifierWithParent; import io.evitadb.api.requestResponse.data.AttributesContract; import io.evitadb.api.requestResponse.data.AssociatedDataContract; import io.evitadb.api.requestResponse.data.ReferenceContract; @@ -77,6 +78,7 @@ import java.util.ArrayList; import java.util.Currency; import java.lang.AutoCloseable; +import java.util.function.Consumer; import java.math.BigDecimal; import java.time.LocalTime; import java.time.LocalDateTime; From fd79379418e167ff9ec932e295af6b0a24ebee2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Mon, 11 Dec 2023 17:12:17 +0100 Subject: [PATCH 21/52] docs(#200): grammar fixes so it passes syntax check --- documentation/user/en/query/basics.md | 8 ++++---- .../user/en/query/examples/complexGrammar.evitaql | 2 +- .../user/en/query/examples/complexGrammar.graphql | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/documentation/user/en/query/basics.md b/documentation/user/en/query/basics.md index 45fc285c6..ff515390e 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: `collection`, `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.evitaql b/documentation/user/en/query/examples/complexGrammar.evitaql index b77ef50df..5f1a63f54 100644 --- a/documentation/user/en/query/examples/complexGrammar.evitaql +++ b/documentation/user/en/query/examples/complexGrammar.evitaql @@ -8,7 +8,7 @@ query( ), orderBy( attributeNatural("code", ASC), - attributeNatural("priority", DESC) + attributeNatural("catalogNumber", DESC) ), require( entityFetch( diff --git a/documentation/user/en/query/examples/complexGrammar.graphql b/documentation/user/en/query/examples/complexGrammar.graphql index 4bf443500..8e4ba6e03 100644 --- a/documentation/user/en/query/examples/complexGrammar.graphql +++ b/documentation/user/en/query/examples/complexGrammar.graphql @@ -6,7 +6,7 @@ }, orderBy: [ { attributeCodeNatural: ASC }, - { attributePriorityNatural: DESC } + { attributeCatalogNumberNatural: DESC } ] ) { recordPage { From 20705321c55b88b2fd9fd2155868fc102db80a28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Novotn=C3=BD?= Date: Mon, 11 Dec 2023 17:46:10 +0100 Subject: [PATCH 22/52] docs: merge with dev --- .../GrpcAttributeUniquenessType.java | 177 +++++++++++++++++ .../GrpcGlobalAttributeUniquenessType.java | 179 ++++++++++++++++++ 2 files changed, 356 insertions(+) create mode 100644 evita_external_api/evita_external_api_grpc/shared/src/main/java/io/evitadb/externalApi/grpc/generated/GrpcAttributeUniquenessType.java create mode 100644 evita_external_api/evita_external_api_grpc/shared/src/main/java/io/evitadb/externalApi/grpc/generated/GrpcGlobalAttributeUniquenessType.java diff --git a/evita_external_api/evita_external_api_grpc/shared/src/main/java/io/evitadb/externalApi/grpc/generated/GrpcAttributeUniquenessType.java b/evita_external_api/evita_external_api_grpc/shared/src/main/java/io/evitadb/externalApi/grpc/generated/GrpcAttributeUniquenessType.java new file mode 100644 index 000000000..3c08d5aa6 --- /dev/null +++ b/evita_external_api/evita_external_api_grpc/shared/src/main/java/io/evitadb/externalApi/grpc/generated/GrpcAttributeUniquenessType.java @@ -0,0 +1,177 @@ +/* + * + * _ _ ____ ____ + * _____ _(_) |_ __ _| _ \| __ ) + * / _ \ \ / / | __/ _` | | | | _ \ + * | __/\ V /| | || (_| | |_| | |_) | + * \___| \_/ |_|\__\__,_|____/|____/ + * + * Copyright (c) 2023 + * + * 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/main/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. + */ + +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: GrpcEnums.proto + +package io.evitadb.externalApi.grpc.generated; + +/** + *
+ * This enum represents the uniqueness type of an {@link AttributeSchema}. It is used to determine whether the attribute
+ * value must be unique among all the entity attributes of this type or whether it must be unique only among attributes
+ * of the same locale.
+ * 
+ * + * Protobuf enum {@code io.evitadb.externalApi.grpc.generated.GrpcAttributeUniquenessType} + */ +public enum GrpcAttributeUniquenessType + implements com.google.protobuf.ProtocolMessageEnum { + /** + *
+   * The attribute is not unique (default).
+   * 
+ * + * NOT_UNIQUE = 0; + */ + NOT_UNIQUE(0), + /** + *
+   * The attribute value must be unique among all the entities of the same collection.
+   * 
+ * + * UNIQUE_WITHIN_COLLECTION = 1; + */ + UNIQUE_WITHIN_COLLECTION(1), + /** + *
+   * The localized attribute value must be unique among all values of the same {@link Locale} among all the entities
+   * using of the same collection.
+   * 
+ * + * UNIQUE_WITHIN_COLLECTION_LOCALE = 2; + */ + UNIQUE_WITHIN_COLLECTION_LOCALE(2), + UNRECOGNIZED(-1), + ; + + /** + *
+   * The attribute is not unique (default).
+   * 
+ * + * NOT_UNIQUE = 0; + */ + public static final int NOT_UNIQUE_VALUE = 0; + /** + *
+   * The attribute value must be unique among all the entities of the same collection.
+   * 
+ * + * UNIQUE_WITHIN_COLLECTION = 1; + */ + public static final int UNIQUE_WITHIN_COLLECTION_VALUE = 1; + /** + *
+   * The localized attribute value must be unique among all values of the same {@link Locale} among all the entities
+   * using of the same collection.
+   * 
+ * + * UNIQUE_WITHIN_COLLECTION_LOCALE = 2; + */ + public static final int UNIQUE_WITHIN_COLLECTION_LOCALE_VALUE = 2; + + + public final int getNumber() { + if (this == UNRECOGNIZED) { + throw new java.lang.IllegalArgumentException( + "Can't get the number of an unknown enum value."); + } + return value; + } + + /** + * @param value The numeric wire value of the corresponding enum entry. + * @return The enum associated with the given numeric wire value. + * @deprecated Use {@link #forNumber(int)} instead. + */ + @java.lang.Deprecated + public static GrpcAttributeUniquenessType valueOf(int value) { + return forNumber(value); + } + + /** + * @param value The numeric wire value of the corresponding enum entry. + * @return The enum associated with the given numeric wire value. + */ + public static GrpcAttributeUniquenessType forNumber(int value) { + switch (value) { + case 0: return NOT_UNIQUE; + case 1: return UNIQUE_WITHIN_COLLECTION; + case 2: return UNIQUE_WITHIN_COLLECTION_LOCALE; + default: return null; + } + } + + public static com.google.protobuf.Internal.EnumLiteMap + internalGetValueMap() { + return internalValueMap; + } + private static final com.google.protobuf.Internal.EnumLiteMap< + GrpcAttributeUniquenessType> internalValueMap = + new com.google.protobuf.Internal.EnumLiteMap() { + public GrpcAttributeUniquenessType findValueByNumber(int number) { + return GrpcAttributeUniquenessType.forNumber(number); + } + }; + + public final com.google.protobuf.Descriptors.EnumValueDescriptor + getValueDescriptor() { + if (this == UNRECOGNIZED) { + throw new java.lang.IllegalStateException( + "Can't get the descriptor of an unrecognized enum value."); + } + return getDescriptor().getValues().get(ordinal()); + } + public final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptorForType() { + return getDescriptor(); + } + public static final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptor() { + return io.evitadb.externalApi.grpc.generated.GrpcEnums.getDescriptor().getEnumTypes().get(1); + } + + private static final GrpcAttributeUniquenessType[] VALUES = values(); + + public static GrpcAttributeUniquenessType valueOf( + com.google.protobuf.Descriptors.EnumValueDescriptor desc) { + if (desc.getType() != getDescriptor()) { + throw new java.lang.IllegalArgumentException( + "EnumValueDescriptor is not for this type."); + } + if (desc.getIndex() == -1) { + return UNRECOGNIZED; + } + return VALUES[desc.getIndex()]; + } + + private final int value; + + private GrpcAttributeUniquenessType(int value) { + this.value = value; + } + + // @@protoc_insertion_point(enum_scope:io.evitadb.externalApi.grpc.generated.GrpcAttributeUniquenessType) +} + diff --git a/evita_external_api/evita_external_api_grpc/shared/src/main/java/io/evitadb/externalApi/grpc/generated/GrpcGlobalAttributeUniquenessType.java b/evita_external_api/evita_external_api_grpc/shared/src/main/java/io/evitadb/externalApi/grpc/generated/GrpcGlobalAttributeUniquenessType.java new file mode 100644 index 000000000..b3336ec70 --- /dev/null +++ b/evita_external_api/evita_external_api_grpc/shared/src/main/java/io/evitadb/externalApi/grpc/generated/GrpcGlobalAttributeUniquenessType.java @@ -0,0 +1,179 @@ +/* + * + * _ _ ____ ____ + * _____ _(_) |_ __ _| _ \| __ ) + * / _ \ \ / / | __/ _` | | | | _ \ + * | __/\ V /| | || (_| | |_| | |_) | + * \___| \_/ |_|\__\__,_|____/|____/ + * + * Copyright (c) 2023 + * + * 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/main/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. + */ + +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: GrpcEnums.proto + +package io.evitadb.externalApi.grpc.generated; + +/** + *
+ * This enum represents the uniqueness type of an {@link GlobalAttributeSchema}. It is used to determine whether
+ * the attribute value must be unique among all the entities using this {@link GlobalAttributeSchema} or whether it
+ * must be unique only among entities of the same locale.
+ * 
+ * + * Protobuf enum {@code io.evitadb.externalApi.grpc.generated.GrpcGlobalAttributeUniquenessType} + */ +public enum GrpcGlobalAttributeUniquenessType + implements com.google.protobuf.ProtocolMessageEnum { + /** + *
+   * The attribute is not unique (default).
+   * 
+ * + * NOT_GLOBALLY_UNIQUE = 0; + */ + NOT_GLOBALLY_UNIQUE(0), + /** + *
+   * The attribute value (either localized or non-localized) must be unique among all values among all the entities
+   * using this {@link GlobalAttributeSchema} in the entire catalog.
+   * 
+ * + * UNIQUE_WITHIN_CATALOG = 1; + */ + UNIQUE_WITHIN_CATALOG(1), + /** + *
+   * The localized attribute value must be unique among all values of the same {@link Locale} among all the entities
+   * using this {@link GlobalAttributeSchema} in the entire catalog.
+   * 
+ * + * UNIQUE_WITHIN_CATALOG_LOCALE = 2; + */ + UNIQUE_WITHIN_CATALOG_LOCALE(2), + UNRECOGNIZED(-1), + ; + + /** + *
+   * The attribute is not unique (default).
+   * 
+ * + * NOT_GLOBALLY_UNIQUE = 0; + */ + public static final int NOT_GLOBALLY_UNIQUE_VALUE = 0; + /** + *
+   * The attribute value (either localized or non-localized) must be unique among all values among all the entities
+   * using this {@link GlobalAttributeSchema} in the entire catalog.
+   * 
+ * + * UNIQUE_WITHIN_CATALOG = 1; + */ + public static final int UNIQUE_WITHIN_CATALOG_VALUE = 1; + /** + *
+   * The localized attribute value must be unique among all values of the same {@link Locale} among all the entities
+   * using this {@link GlobalAttributeSchema} in the entire catalog.
+   * 
+ * + * UNIQUE_WITHIN_CATALOG_LOCALE = 2; + */ + public static final int UNIQUE_WITHIN_CATALOG_LOCALE_VALUE = 2; + + + public final int getNumber() { + if (this == UNRECOGNIZED) { + throw new java.lang.IllegalArgumentException( + "Can't get the number of an unknown enum value."); + } + return value; + } + + /** + * @param value The numeric wire value of the corresponding enum entry. + * @return The enum associated with the given numeric wire value. + * @deprecated Use {@link #forNumber(int)} instead. + */ + @java.lang.Deprecated + public static GrpcGlobalAttributeUniquenessType valueOf(int value) { + return forNumber(value); + } + + /** + * @param value The numeric wire value of the corresponding enum entry. + * @return The enum associated with the given numeric wire value. + */ + public static GrpcGlobalAttributeUniquenessType forNumber(int value) { + switch (value) { + case 0: return NOT_GLOBALLY_UNIQUE; + case 1: return UNIQUE_WITHIN_CATALOG; + case 2: return UNIQUE_WITHIN_CATALOG_LOCALE; + default: return null; + } + } + + public static com.google.protobuf.Internal.EnumLiteMap + internalGetValueMap() { + return internalValueMap; + } + private static final com.google.protobuf.Internal.EnumLiteMap< + GrpcGlobalAttributeUniquenessType> internalValueMap = + new com.google.protobuf.Internal.EnumLiteMap() { + public GrpcGlobalAttributeUniquenessType findValueByNumber(int number) { + return GrpcGlobalAttributeUniquenessType.forNumber(number); + } + }; + + public final com.google.protobuf.Descriptors.EnumValueDescriptor + getValueDescriptor() { + if (this == UNRECOGNIZED) { + throw new java.lang.IllegalStateException( + "Can't get the descriptor of an unrecognized enum value."); + } + return getDescriptor().getValues().get(ordinal()); + } + public final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptorForType() { + return getDescriptor(); + } + public static final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptor() { + return io.evitadb.externalApi.grpc.generated.GrpcEnums.getDescriptor().getEnumTypes().get(2); + } + + private static final GrpcGlobalAttributeUniquenessType[] VALUES = values(); + + public static GrpcGlobalAttributeUniquenessType valueOf( + com.google.protobuf.Descriptors.EnumValueDescriptor desc) { + if (desc.getType() != getDescriptor()) { + throw new java.lang.IllegalArgumentException( + "EnumValueDescriptor is not for this type."); + } + if (desc.getIndex() == -1) { + return UNRECOGNIZED; + } + return VALUES[desc.getIndex()]; + } + + private final int value; + + private GrpcGlobalAttributeUniquenessType(int value) { + this.value = value; + } + + // @@protoc_insertion_point(enum_scope:io.evitadb.externalApi.grpc.generated.GrpcGlobalAttributeUniquenessType) +} + From 76af6af0c25112b19139c0c4fcc3fd9b26981933 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Mon, 11 Dec 2023 17:48:15 +0100 Subject: [PATCH 23/52] fix(#200): additional default `call` method --- .../src/main/java/io/evitadb/test/client/GraphQLClient.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/evita_test_support/src/main/java/io/evitadb/test/client/GraphQLClient.java b/evita_test_support/src/main/java/io/evitadb/test/client/GraphQLClient.java index d6bbba57c..05a01a406 100644 --- a/evita_test_support/src/main/java/io/evitadb/test/client/GraphQLClient.java +++ b/evita_test_support/src/main/java/io/evitadb/test/client/GraphQLClient.java @@ -49,6 +49,11 @@ public GraphQLClient(@Nonnull String url, boolean validateSsl) { super(url, validateSsl); } + @Nonnull + public JsonNode call(@Nonnull String document) { + return call("/gql/evita", document); + } + @Nonnull public JsonNode call(@Nonnull String instancePath, @Nonnull String document) { HttpURLConnection connection = null; From 4a7951daddb331790d992ca3aa72d36695a06783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Tue, 12 Dec 2023 08:38:37 +0100 Subject: [PATCH 24/52] docs: write-data refactor --- documentation/user/en/use/api/write-data.md | 598 +++++++------------- 1 file changed, 204 insertions(+), 394 deletions(-) diff --git a/documentation/user/en/use/api/write-data.md b/documentation/user/en/use/api/write-data.md index 7dd546d55..a0c5e4ad7 100644 --- a/documentation/user/en/use/api/write-data.md +++ b/documentation/user/en/use/api/write-data.md @@ -1,7 +1,7 @@ --- title: Write data perex: | - This article contains the main principles for authoring data in evitaDB, the description of the data API regarding + This article contains the main principles for authoring data in evitaDB, the description of the data API regarding entity upsert and deletion and related recommendations. date: '17.1.2023' author: 'Ing. Jan Novotný' @@ -9,36 +9,13 @@ proofreading: 'done' preferredLang: 'java' --- - - -## Indexing modes - -evitaDB assumes that it will not be the primary data store for your data. Because evitaDB is a relatively new database -implementation, it's wise to store your primary data in a mature, time-tested and proven technology such as a relational -database. evitaDB brings you the necessary low-latency and e-commerce-optimized feature as a secondary fast-read index -where you mirror/transform the data from your primary data store. We would like to become your primary data store one -day, but let's be honest - we're not there yet. - -This reasoning led us to design two different types of [entity data](../data-model.md) ingestion and corresponding -catalog states: - -- [bulk indexing](#bulk-indexing), state: `WARMUP` -- [incremental indexing](#incremental-indexing), state `ALIVE` - -### Bulk indexing - -Bulk indexing is used to quickly index large amounts of source data. It's used for initial catalog creation from -external (primary) data stores. It doesn't need to support transactions and allows only a single session (single thread) -to be opened from the client side. The catalog is in a so-called `WARMUP` state -(evita_api/src/main/java/io/evitadb/api/CatalogState.java). The client can both write and -query the written data, but no other client can open another session because the consistency of the data could not be -guaranteed for them. The goal here is to index hundreds or thousands of entities per second. + -If the database crashes during this initial bulk indexing, the state and consistency of the data must be considered -corrupted, and the entire catalog should be dumped and rebuilt from scratch. Since there is no client other than the -one writing the data, we can afford to do this. +Unfortunately, it is currently not possible to write data using EvitaQL. This extension is also not planned to be +implemented in the near future, because we believe that sufficient options (Java, GraphQL, REST API, gRPC and C#) are available. + ## Indexing modes @@ -52,15 +29,16 @@ day, but let's be honest - we're not there yet. This reasoning led us to design two different types of [entity data](../data-model.md) ingestion and corresponding catalog states: -- [bulk indexing](#bulk-indexing), state: `Warmup` -- [incremental indexing](#incremental-indexing), state `Alive` +- [bulk indexing](#bulk-indexing), state: `WARMUP``Warmup` +- [incremental indexing](#incremental-indexing), state: `ALIVE``Alive` ### Bulk indexing Bulk indexing is used to quickly index large amounts of source data. It's used for initial catalog creation from external (primary) data stores. It doesn't need to support transactions and allows only a single session (single thread) -to be opened from the client side. The catalog is in a so-called `Warmup` state -(EvitaDB.Client/Session/CatalogState.cs). The client can both write and +to be opened from the client side. The catalog is in a so-called `WARMUP``Warmup` state +(evita_api/src/main/java/io/evitadb/api/CatalogState.javaEvitaDB.Client/Session/CatalogState.cs). +The client can both write and query the written data, but no other client can open another session because the consistency of the data could not be guaranteed for them. The goal here is to index hundreds or thousands of entities per second. @@ -70,58 +48,36 @@ one writing the data, we can afford to do this. - + -Any newly created catalog starts in `WARMUP` state and must be manually switched to *transactional* mode by executing: +Any newly created catalog starts in `WARMUP``Warmup` +state and must be manually switched to *transactional* mode by executing: [Termination of warm-up mode](/documentation/user/en/use/api/example/finalization-of-warmup-mode.java) -The `goLiveAndClose` method sets the catalog to `ALIVE` (transactional) state and closes the current session. From this -moment on, multiple clients can open read-only or read-write sessions in parallel to this particular catalog. - - - - -Any newly created catalog starts in `Warmup` state and must be manually switched to *transactional* mode by executing: - - - -[Termination of warm-up mode](/documentation/user/en/use/api/example/finalization-of-warmup-mode.cs) - - -The `GoLiveAndClose` method sets the catalog to `Alive` (transactional) state and closes the current session. From this -moment on, multiple clients can open read-only or read-write sessions in parallel to this particular catalog. +The `goLiveAndClose``GoLiveAndClose` +method sets the catalog to `ALIVE``Alive` +(transactional) state and closes the current session. From this moment on, multiple clients can open read-only or read-write +sessions in parallel to this particular catalog. - - -Any newly created catalog starts in `WARMUP` state and must be manually switched to *transactional* mode using the -[system API](/documentation/user/en/use/connectors/graphql.md#graphql-api-instances) by executing: - - - -[Termination of warm-up mode](/documentation/user/en/use/api/example/finalization-of-warmup-mode.graphql) - - -The `switchCatalogToAliveState` mutation sets the catalog to `ALIVE` (transactional) state. From this -moment on, multiple clients can send queries or mutations requests in parallel to this particular catalog. - - - + Any newly created catalog starts in `WARMUP` state and must be manually switched to *transactional* mode using the -[system API](/documentation/user/en/use/connectors/rest.md#rest-api-instances) by executing: +[system API](/documentation/user/en/use/connectors/graphql.md#graphql-api-instances)(/documentation/user/en/use/connectors/rest.md#rest-api-instances) +by executing: [Termination of warm-up mode](/documentation/user/en/use/api/example/finalization-of-warmup-mode.graphql) -The `/catalogs/{catalog-name}` endpoint with `PATCH` method sets the catalog to `ALIVE` (transactional) state. From this -moment on, multiple clients can send fetching or mutating requests in parallel to this particular catalog. +The `switchCatalogToAliveState` mutation`/catalogs/{catalog-name}` endpoint with `PATCH` method +sets the catalog to `ALIVE` (transactional) state. From this moment on, multiple clients can send queries or mutations +requests in parallel to this particular catalog. @@ -130,14 +86,14 @@ moment on, multiple clients can send fetching or mutating requests in parallel t ### Incremental indexing The incremental indexing mode is used to keep the index up to date with the primary data store during its lifetime. -We expect some form of [change data capture](https://en.wikipedia.org/wiki/Change_data_capture) process to be built into -the primary data store. One of the more interesting recent developments in this area is -[the Debezium project](https://debezium.io/), which allows changes from primary data stores to be streamed to secondary +We expect some form of [change data capture](https://en.wikipedia.org/wiki/Change_data_capture) process to be built into +the primary data store. One of the more interesting recent developments in this area is +[the Debezium project](https://debezium.io/), which allows changes from primary data stores to be streamed to secondary indexes fairly easily. There might be multiple clients reading & writing data to the same catalog when it is in `ALIVE` state. Each catalog -update is wrapped into a *transaction* that meets -[the snapshot isolation level](https://en.wikipedia.org/wiki/Snapshot_isolation). More details about transaction +update is wrapped into a *transaction* that meets +[the snapshot isolation level](https://en.wikipedia.org/wiki/Snapshot_isolation). More details about transaction handling is in [separate chapter](../../deep-dive/transactions.md). ## Model characteristics @@ -154,7 +110,7 @@ concurrent access (in other words, entities can be cached without fear of race c -All model classes are described by interfaces, and there should be no reason to use or instantiate direct classes. +All model classes are described by interfaces, and there should be no reason to use or instantiate direct classes. Interfaces follow this structure:
@@ -166,60 +122,39 @@ Interfaces follow this structure:
combines **Contract** and **Editor** interfaces, and it is actually used to create the instance
-When you create new entity using evitaDB API, you obtain a builder, and you can immediately start setting the data +When you create new entity using evitaDB API, you obtain a builder, and you can immediately start setting the data to the entity and then store the entity to the database: - [Creating new entity returns a builder](/documentation/user/en/use/api/example/create-new-entity-shortened.java) - - - - -[Creating new entity returns a builder](/documentation/user/en/use/api/example/create-new-entity-shortened.cs) - - -When you read existing entity from the catalog, you obtain read-only +When you read existing entity from the catalog, you obtain read-only evita_api/src/main/java/io/evitadb/api/requestResponse/data/SealedEntity.javaEvitaDB.Client/Models/Data/ISealedEntity.cs, which is basically a contract interface with a few methods allowing you to convert it to the builder instance that can be used for updating the data: - + [Retrieving existing entity returns a sealed entity](/documentation/user/en/use/api/example/update-existing-entity-shortened.java) - - - -[Retrieving existing entity returns a sealed entity](/documentation/user/en/use/api/example/update-existing-entity-shortened.cs) - - - + -In the GraphQL API, the immutability is implicit by design. You may be able to modify the returned entity objects in your client +In the GraphQLREST API, +the immutability is implicit by design. You may be able to modify the returned entity objects in your client application, but these changes cannot be propagated to the evitaDB server, so it is recommended to make your client model immutable as well (see the Java API for inspiration). The only way to modify the data is to use the -[catalog data API](/documentation/user/en/use/connectors/graphql.md#graphql-api-instances) and manually send evitaDB mutations -with individual changes using one of the `updateCollectionName` GraphQL mutations specific to your selected -[entity collection](/documentation/user/en/use/data-model.md#collection). - - - - -In the REST API, the immutability is implicit by design. You may be able to modify the returned entity objects in your client -application, but these changes cannot be propagated to the evitaDB server, so it is encouraged to make your client model -immutable as well (see the Java API for inspiration). The only way to modify the data is to use the -[catalog API](/documentation/user/en/use/connectors/rest.md#rest-api-instances) and manually send evitaDB mutations -with individual changes using one of the REST endpoints for modifying data of your selected -[entity collection](/documentation/user/en/use/data-model.md#collection). +[catalog data API](/documentation/user/en/use/connectors/graphql.md#graphql-api-instances) +[catalog API](/documentation/user/en/use/connectors/rest.md#rest-api-instances) +and manually send evitaDB mutations with individual changes using one of the +`updateCollectionName` GraphQL mutations specific to +REST endpoints for modifying data of your selected [entity collection](/documentation/user/en/use/data-model.md#collection). @@ -227,7 +162,7 @@ with individual changes using one of the REST endpoints for modifying data of yo ### Versioning -All model classes are versioned - in other words, when a model instance is modified, the version number of the new +All model classes are versioned - in other words, when a model instance is modified, the version number of the new instance created from that modified state is incremented by one. @@ -235,7 +170,7 @@ instance created from that modified state is incremented by one. Version information is available not only at the evita_api/src/main/java/io/evitadb/api/requestResponse/data/EntityContract.javaEvitaDB.Client/Models/Data/IEntity.cs level, -but also at more granular levels (such as +but also at more granular levels (such as evita_api/src/main/java/io/evitadb/api/requestResponse/data/AttributesContract.javaEvitaDB.Client/Models/Data/IAttributes.cs, evita_api/src/main/java/io/evitadb/api/requestResponse/data/ReferenceContract.javaEvitaDB.Client/Models/Data/IReference.cs, or evita_api/src/main/java/io/evitadb/api/requestResponse/data/AssociatedDataContract.javaEvitaDB.Client/Models/Data/IAssociatedData.cs). @@ -245,7 +180,7 @@ All model classes that support versioning implement the -Version information is available at the entity level. +Version information is available at the entity level. @@ -264,7 +199,7 @@ The version information serves two purposes: -Since the entity is *immutable* and *versioned* the default implementation of the `hashCode` and `equals` takes these +Since the entity is *immutable* and *versioned* the default implementation of the `hashCode` and `equals` takes these three components into account: 1. entity type @@ -290,19 +225,19 @@ interface and implemented by the The communication with the evitaDB instance always takes place via the evita_api/src/main/java/io/evitadb/api/EvitaSessionContract.javaEvitaDB.Client/EvitaClientSession.cs interface. -Session is a single-threaded communication channel identified by a unique +Session is a single-threaded communication channel identified by a unique [random UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier). -In the web environment it's a good idea to have one session per request, in batch processing it's recommended to keep +In the web environment it's a good idea to have one session per request, in batch processing it's recommended to keep a single session for an entire batch. -To conserve resources, the server automatically closes sessions after a period of inactivity. -The interval is set by default to `60 seconds` but +To conserve resources, the server automatically closes sessions after a period of inactivity. +The interval is set by default to `60 seconds` but [it can be changed](https://evitadb.io/documentation/operate/configure#server-configuration) to different value. The inactivity means that there is no activity recorded on the evita_api/src/main/java/io/evitadb/api/EvitaSessionContract.javaEvitaDB.Client/EvitaClientSession.cs interface. If you need -to artificially keep session alive you need to periodically call some method without side-effects on the session +to artificially keep session alive you need to periodically call some method without side-effects on the session interface, such as:
@@ -310,7 +245,7 @@ interface, such as:
In case of embedded evitaDB usage.
`getEntityCollectionSize`
- In case of remote use of evitaDB. In this case we really need to call some method that triggers the network + In case of remote use of evitaDB. In this case we really need to call some method that triggers the network communication. Many methods in evita_api/src/main/java/io/evitadb/api/EvitaSessionContract.javaEvitaDB.Client/EvitaClientSession.cs return only locally cached results to avoid expensive and unnecessary network calls. @@ -319,48 +254,39 @@ interface, such as: evita_api/src/main/java/io/evitadb/api/TransactionContract.javaEvitaDB.Client/EvitaClientTransaction.cs is an envelope for a "unit -of work" with evitaDB. A transaction exists within a session and is guaranteed to have -[the snapshot isolation level](https://en.wikipedia.org/wiki/Snapshot_isolation) for reads. The changes in -a transaction are always isolated from other transactions and become visible only after the transaction has been -committed. If the transaction is marked as *rollback only*, all changes will be discarded on transaction closing and -will never reach the shared database state. There can be at most one active transaction in a session, but there can +of work" with evitaDB. A transaction exists within a session and is guaranteed to have +[the snapshot isolation level](https://en.wikipedia.org/wiki/Snapshot_isolation) for reads. The changes in +a transaction are always isolated from other transactions and become visible only after the transaction has been +committed. If the transaction is marked as *rollback only*, all changes will be discarded on transaction closing and +will never reach the shared database state. There can be at most one active transaction in a session, but there can be multiple successor transactions during the session's lifetime. - - -The communication with the evitaDB instance using the GraphQL API always uses some kind of session. In the case of the GraphQL API, -a session is a per-request communication channel that is used in the background. - -A transaction is an envelope for a "unit of work" with evitaDB. -In the GraphQL API, a transaction exists for the duration of a session, or more precisely a GraphQL API request, and it is -guaranteed to have [the snapshot isolation level](https://en.wikipedia.org/wiki/Snapshot_isolation) for reads. The changes in -a transaction are always isolated from other transactions and become visible only after the transaction has been -committed, i.e. the request to the GraphQL API has been processed and was successful. If a GraphQL API request results in -any kind of error, the transaction is automatically rolled back. - - - + -The communication with the evitaDB instance using the REST API always uses some kind of session. In the case of the REST API, +The communication with the evitaDB instance using the GraphQLREST +API always uses some kind of session. In the case of the GraphQLREST API, a session is a per-request communication channel that is used in the background. A transaction is an envelope for a "unit of work" with evitaDB. -In the REST API, a transaction exists for the duration of a session, or more precisely a REST API request, and it is +In the GraphQLREST API, +a transaction exists for the duration of a session, or more precisely a GraphQLREST +API request, and it is guaranteed to have [the snapshot isolation level](https://en.wikipedia.org/wiki/Snapshot_isolation) for reads. The changes in a transaction are always isolated from other transactions and become visible only after the transaction has been -committed, i.e. the request to the REST API has been processed and was successful. If a REST API request results in -any kind of error, the transaction is automatically rolled back. +committed, i.e. the request to the GraphQLREST +API has been processed and was successful. If a GraphQLREST +API request results in any kind of error, the transaction is automatically rolled back. -Parallel transaction handling hasn't been finalized yet, and is scheduled to be finalized in -[issue #16](https://github.com/FgForrest/evitaDB/issues/16). Until this issue is resolved, you must ensure that only -a single client is writing to the catalog in parallel. Other clients may have been reading in parallel using read-only +Parallel transaction handling hasn't been finalized yet, and is scheduled to be finalized in +[issue #16](https://github.com/FgForrest/evitaDB/issues/16). Until this issue is resolved, you must ensure that only +a single client is writing to the catalog in parallel. Other clients may have been reading in parallel using read-only sessions, but the writer must be only one. @@ -386,7 +312,7 @@ evitaDB recognizes two types of sessions:
read-only
Read-only sessions are opened by calling GraphQL queries, i.e. `getCollectionName`, `listCollectionName`, - `queryCollectionName` and so on. No write operations are allowed in a read-only session + `queryCollectionName` and so on. No write operations are allowed in a read-only session which allows evitaDB to optimize its behavior when working with the database.
read-write
Read-write sessions are opened by calling GraphQL mutations, i.e. `upsertCollectionName`, `deleteCollectionName` @@ -409,19 +335,20 @@ evitaDB recognizes two types of sessions: -In the future, the read-only sessions can be distributed to multiple read nodes, while the read-write sessions must +In the future, the read-only sessions can be distributed to multiple read nodes, while the read-write sessions must talk to the master node. - + #### Unsafe session lifecycle -We recommend to open sessions using `queryCatalog` / `updateCatalog` methods that accept a lambda function to execute -your business logic. This way evitaDB can safely handle the lifecycle management of *sessions* & *transactions*. -This approach is not always acceptable - for example, if your application needs to be integrated into an existing -framework that only provides a lifecycle callback methods, there is no way to "wrap" the entire business logic in +We recommend to open sessions using `queryCatalog` / `updateCatalog` +`QueryCatalog` / `UpdateCatalog` methods that accept a lambda function to execute +your business logic. This way evitaDB can safely handle the lifecycle management of *sessions* & *transactions*. +This approach is not always acceptable - for example, if your application needs to be integrated into an existing +framework that only provides a lifecycle callback methods, there is no way to "wrap" the entire business logic in the lambda function. That's why there is an alternative - not so secure - approach to handling sessions and transactions: @@ -432,20 +359,23 @@ That's why there is an alternative - not so secure - approach to handling sessio -If you use manual *session / transaction* handling, you must ensure that for every scope opening there is +If you use manual *session / transaction* handling, you must ensure that for every scope opening there is a corresponding closing (even if an exception occurs during your business logic call). -Both evita_api/src/main/java/io/evitadb/api/EvitaSessionContract.java and -evita_api/src/main/java/io/evitadb/api/TransactionContract.java implement Java -`Autocloseable` interface, so you can use them this way: +Both evita_api/src/main/java/io/evitadb/api/EvitaSessionContract.java +EvitaDB.Client/EvitaClientSession.cs and +evita_api/src/main/java/io/evitadb/api/TransactionContract.java implement Java +`Autocloseable`EvitaDB.Client/EvitaClientTransaction.cs implement C# `IDisposable` +interface, so you can use them this way: [Get advantage of Autocloseable behaviour](/documentation/user/en/use/api/example/autocloseable-transaction-management.java) -This approach is safe, but has the same disadvantage as using `queryCatalog` / `updateCatalog` methods - you need to +This approach is safe, but has the same disadvantage as using `queryCatalog` / `updateCatalog` +`QueryCatalog` / `UpdateCatalog` methods - you need to have all the business logic executable within the same block. #### Dry-run session @@ -457,56 +387,6 @@ For testing purposes, there is a special flag that can be used when opening a ne [Opening dry-run session](/documentation/user/en/use/api/example/dry-run-session.java) -In this session, all transactions will automatically have a *rollback* flag set when they are opened, without the need -to set the rollback flag manually. This fact greatly simplifies -[transaction rollback on test teardown pattern](http://xunitpatterns.com/Transaction%20Rollback%20Teardown.html) when -implementing your tests, or can be useful if you want to ensure that the changes are not committed in a particular -session, and you don't have easy access to the places where the transaction is opened. - - - - -#### Unsafe session lifecycle - -We recommend to open sessions using `QueryCatalog` / `UpdateCatalog` methods that accept a lambda function to execute -your business logic. This way evitaDB can safely handle the lifecycle management of *sessions* & *transactions*. -This approach is not always acceptable - for example, if your application needs to be integrated into an existing -framework that only provides a lifecycle callback methods, there is no way to "wrap" the entire business logic in -a delegate function. - -That's why there is an alternative - not so secure - approach to handling sessions and transactions: - - - -[Manual session and transaction handling](/documentation/user/en/use/api/example/manual-transaction-management.cs) - - - -If you use manual *session / transaction* handling, you must ensure that for every scope opening there is -a corresponding closing (even if an exception occurs during your business logic call). - - -Both EvitaDB.Client/EvitaClientSession.cs and -EvitaDB.Client/EvitaClientTransaction.cs implement C# `IDisposable` -interface, so you can use them this way: - - - -[Get advantage of Autocloseable behaviour](/documentation/user/en/use/api/example/autocloseable-transaction-management.cs) - - -This approach is safe, but has the same disadvantage as using `QueryCatalog` / `UpdateCatalog` methods - you need to -have all the business logic executable within the same block. - -#### Dry-run session - -For testing purposes, there is a special flag that can be used when opening a new session - a **dry run** flag: - - - -[Opening dry-run session](/documentation/user/en/use/api/example/dry-run-session.cs) - - In this session, all transactions will automatically have a *rollback* flag set when they are opened, without the need to set the rollback flag manually. This fact greatly simplifies [transaction rollback on test teardown pattern](http://xunitpatterns.com/Transaction%20Rollback%20Teardown.html) when @@ -521,10 +401,11 @@ session, and you don't have easy access to the places where the transaction is o - + It's expected that most of the entity instances will be created by the evitaDB service classes - such as -evita_api/src/main/java/io/evitadb/api/EvitaSessionContract.java +evita_api/src/main/java/io/evitadb/api/EvitaSessionContract.java +EvitaDB.Client/EvitaClientSession.cs Anyway, there is also the [possibility of creating them directly](#creating-entities-in-detached-mode). Usually the entity creation will look like this: @@ -538,7 +419,7 @@ This way, the created entity can be immediately checked against the schema. This and it may be split into several parts, which will reveal the "builder" used in the process. When you need to alter existing entity, you first fetch it from the server, open for writing (which converts it to -the builder wrapper), modify it, and finally collect the changes and send them to the server. +the builder wrapper), modify it, and finally collect the changes and send them to the server. @@ -546,74 +427,112 @@ the builder wrapper), modify it, and finally collect the changes and send them t -The `upsertVia` method is a shortcut for calling `session.upsertEntity(builder.buildChangeSet())`. If you look at the -evita_api/src/main/java/io/evitadb/api/requestResponse/data/BuilderContract.java you'll + +The `upsertVia``UpsertVia +method is a shortcut for calling `session.upsertEntity(builder.buildChangeSet())` +`session.UpsertEntity(builder.BuildChangeSet())`. If you look at the +evita_api/src/main/java/io/evitadb/api/requestResponse/data/BuilderContract.java +EvitaDB.Client/Models/Data/IBuilder.cs you'll see, that you can call on it either: + +
`buildChangeSet`
Creates a stream of *mutations* that represent the changes you've made to the immutable object.
`toInstance`
-
Creates a new version of the immutable entity object with all changes applied. This allows you to create a - new instance of the object locally without sending the changes to the server. When you fetch the same instance +
Creates a new version of the immutable entity object with all changes applied. This allows you to create a + new instance of the object locally without sending the changes to the server. When you fetch the same instance from the server again, you'll see that none of the changes have been applied to the database entity.
-
+
+ +
+ + +
+
`BuildChangeSet`
+
Creates a stream of *mutations* that represent the changes you've made to the immutable object.
+
`ToInstance`
+
Creates a new version of the immutable entity object with all changes applied. This allows you to create a + new instance of the object locally without sending the changes to the server. When you fetch the same instance + from the server again, you'll see that none of the changes have been applied to the database entity.
+
+ +
+ + + evita_api/src/main/java/io/evitadb/api/requestResponse/data/mutation/EntityMutation.java or evita_api/src/main/java/io/evitadb/api/requestResponse/data/BuilderContract.java can be passed to evita_api/src/main/java/io/evitadb/api/EvitaSessionContract.java `upsert` method, -which returns +which returns evita_api/src/main/java/io/evitadb/api/requestResponse/data/structure/EntityReference.java containing only entity type and (possibly assigned) primary key information. You can also use the `upsertAndFetchEntity` -method, which inserts or creates the entity and returns its body in the form and size you specify in your `require` +method, which inserts or creates the entity and returns its body in the form and size you specify in your `require` argument. + + +EvitaDB.Client/Models/Data/Mutations/IEntityMutation.cs or +EvitaDB.Client/Models/Data/IBuilder.cs can be +passed to EvitaDB.Client/EvitaClientSession.cs `Upsert` method, +which returns EvitaDB.Client/Models/Data/Structure/EntityReference.cs +containing only entity type and (possibly assigned) primary key information. You can also use the `UpsertAndFetchEntity` +method, which inserts or creates the entity and returns its body in the form and size you specify in your `Require` +argument. + + +
+ + + #### Custom contracts -Similar to [query data using custom contracts](query-data.md#custom-contracts), you can also create new entities and -modify existing ones using custom contracts. This allows you to completely bypass working with the evitaDB internal -model and stick to your own - domain specific - model. When modeling your read/write contracts, we recommend to stick +Similar to [query data using custom contracts](query-data.md#custom-contracts), you can also create new entities and +modify existing ones using custom contracts. This allows you to completely bypass working with the evitaDB internal +model and stick to your own - domain specific - model. When modeling your read/write contracts, we recommend to stick to the [sealed/open principle](../connectors/java.md#data-modeling-recommendations). -Your write contract will likely extend read contracts using annotations described in -the [schema API](schema-api.md#schema-controlling-annotations) and/or [query data API](query-data.md#custom-contracts). +Your write contract will likely extend read contracts using annotations described in +the [schema API](schema-api.md#schema-controlling-annotations) and/or [query data API](query-data.md#custom-contracts). If you follow the Java Beans naming convention, you don't need to use annotations on write methods, but if you want to -use different names or clarify your write contract, just use the [query data annotations](query-data.md#custom-contracts) +use different names or clarify your write contract, just use the [query data annotations](query-data.md#custom-contracts) on write methods. In some cases, you may want to use the following additional annotations:
evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/CreateWhenMissing.java
- Annotation can be used on methods accepting [Consumer](/https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/function/Consumer.html) + Annotation can be used on methods accepting [Consumer](/https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/function/Consumer.html) type or methods that return/accept your custom contract. When the method is called, the automatic - implementation logic will create a new instance of this contract for you to work with. The new instance is + implementation logic will create a new instance of this contract for you to work with. The new instance is persisted along with the entity that was responsible for creating it (see details in the following paragraphs).
evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/RemoveWhenExists.java
- Annotation can be used on methods and will trigger the removal of the specific entity data - attribute, + Annotation can be used on methods and will trigger the removal of the specific entity data - attribute, associated data, parent reference, entity reference, or price. The removal only affects the entity itself, never the target entity. Physical removal is only performed when the entity itself is upserted to the database.
-Write methods can return several types (in some cases the supported list is even longer - but these cases are described +Write methods can return several types (in some cases the supported list is even longer - but these cases are described in the appropriate sections): - `void` - the method performs the modification and returns no value -- `selfType` - the method executes the modification and returns the reference to the contract itself, which allows +- `selfType` - the method executes the modification and returns the reference to the contract itself, which allows to chain multiple write calls together ([builder pattern](https://blogs.oracle.com/javamagazine/post/exploring-joshua-blochs-builder-design-pattern-in-java)) -In the following sections, we'll describe the behavior of the automatic implementation logic in detail and with +In the following sections, we'll describe the behavior of the automatic implementation logic in detail and with examples: -The examples contain only interface/class definitions since the Java record is read-only. Examples describe -a read/write contract in the same class, which is the simpler approach, but not entirely safe in terms of parallel +The examples contain only interface/class definitions since the Java record is read-only. Examples describe +a read/write contract in the same class, which is the simpler approach, but not entirely safe in terms of parallel access to the data. If you want to follow the recommended [sealed/open principle](../connectors/java.md#data-modeling-recommendations) -you should declare `extends SealedEntity` in the read interface contract +you should declare `extends SealedEntity` in the read interface contract and `extends InstanceEditor` in the write interface contract. @@ -621,19 +540,19 @@ and `extends InstanceEditor` in the write interface contract. When you create new (non-existing) entities using methods annotated with `@CreateWhenMissing`, these entities are held -in local memory and their persistence is delayed until the entity that created them is persisted using -the `upsertDeeply` method. If you don't call this method, or call the simple `upsert` method, the created entities and -references to them will be lost. You may also want to persist them separately or before the main entity that created +in local memory and their persistence is delayed until the entity that created them is persisted using +the `upsertDeeply` method. If you don't call this method, or call the simple `upsert` method, the created entities and +references to them will be lost. You may also want to persist them separately or before the main entity that created them. In this case you can call the `upsert` method on them directly. The API allows you to create an infinite depth chain of dependent entities and the `upsertDeeply` / `upsert` logic will -work correctly at all levels. If you create entity `A` in which you created a reference to entity `B` in which you -created another reference to entity `C`, the `upsertDeeply` method called on entity `A` will persist all three entities -in the correct order (`C`, `B`, `A`). If you call the `upsertDeeply` method on entity `B`, it will only persist +work correctly at all levels. If you create entity `A` in which you created a reference to entity `B` in which you +created another reference to entity `C`, the `upsertDeeply` method called on entity `A` will persist all three entities +in the correct order (`C`, `B`, `A`). If you call the `upsertDeeply` method on entity `B`, it will only persist the sub-entities in the correct order (`C`, `B`). You can also manually call the `upsert` method on entity `C`, then `B` and finally `A`. However, if you persist entity `A` without first persisting entities `B` and `C`, the reference between -`A` and `B` will be dropped. You can still call `upsertDeeply` on entity `B`, which will keep the reference between `B` -and `C`. +`A` and `B` will be dropped. You can still call `upsertDeeply` on entity `B`, which will keep the reference between `B` +and `C`. @@ -697,16 +616,16 @@ to ["complex data type"](../data-types.md#complex-data-types) using [documented ##### Prices To set the entity prices, you could work with -evita_api/src/main/java/io/evitadb/api/requestResponse/data/PriceContract.java data type or -pass all necessary dat in method parameters and annotate the methods with -the evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/Price.java +evita_api/src/main/java/io/evitadb/api/requestResponse/data/PriceContract.java data type or +pass all necessary dat in method parameters and annotate the methods with +the evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/Price.java annotation. You can set (create or update) a single price by its business key, which consists of: - **`priceId`** - number datatype with external price identifier - **`currency`** - [Currency](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Currency.html) or [string](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html) datatype accepting 3-letter currency ISO code - **`priceList`** - [String](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html) data type with price list name -or you can set all prices using the method that takes the array, [Collection](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Collection.html), [List](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/List.html) or +or you can set all prices using the method that takes the array, [Collection](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Collection.html), [List](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/List.html) or [Set](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Set.html) parameter with all entity prices. @@ -742,8 +661,8 @@ annotation or have a corresponding getter (or field) with this annotation in the If the reference has `ZERO_OR_MORE` or `ONE_OR_MORE` cardinality, you can also wrap it in [Collection](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Collection.html) (or its specializations [List](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/List.html) -or [Set](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Set.html)), or pass it as a simple array -to rewrite all the values at once. If the reference has `ZERO_OR_ONE` cardinality and you pass a `NULL` value, +or [Set](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Set.html)), or pass it as a simple array +to rewrite all the values at once. If the reference has `ZERO_OR_ONE` cardinality and you pass a `NULL` value, the reference is automatically removed. @@ -752,77 +671,16 @@ the reference is automatically removed. -#### Creating entities in detached mode - -Entity instances can be created even if no EvitaDB instance is available: - - -[Detached instantiation example](/documentation/user/en/use/api/example/detached-instantiation.java) - - -Although you will probably only take advantage of this approach when writing test cases, it still allows you to create -a stream of mutations that can be sent to and processed by the evitaDB server once you manage to get its instance. - -There is an analogous builder that takes an existing entity and tracks changes made to it. - - - -[Detached existing entity example](/documentation/user/en/use/api/example/detached-existing-entity-instantiation.java) - -
- -It's expected that most of the entity instances will be created by the evitaDB service classes - such as -EvitaDB.Client/EvitaClientSession.cs -Anyway, there is also the [possibility of creating them directly](#creating-entities-in-detached-mode). - -Usually the entity creation will look like this: - - - -[Creating new entity example](/documentation/user/en/use/api/example/create-new-entity.cs) - - -This way, the created entity can be immediately checked against the schema. This form of code is a condensed version, -and it may be split into several parts, which will reveal the "builder" used in the process. - -When you need to alter existing entity, you first fetch it from the server, open for writing (which converts it to -the builder wrapper), modify it, and finally collect the changes and send them to the server. - - - -[Updating existing entity example](/documentation/user/en/use/api/example/update-existing-entity.cs) - - - -The `UpsertVia` method is a shortcut for calling `session.UpsertEntity(builder.BuildChangeSet())`. If you look at the -EvitaDB.Client/Models/Data/IBuilder.cs you'll see, that you can call on it either: - -
-
`BuildChangeSet`
-
Creates a stream of *mutations* that represent the changes you've made to the immutable object.
-
`ToInstance`
-
Creates a new version of the immutable entity object with all changes applied. This allows you to create a - new instance of the object locally without sending the changes to the server. When you fetch the same instance - from the server again, you'll see that none of the changes have been applied to the database entity.
-
-
- -EvitaDB.Client/Models/Data/Mutations/IEntityMutation.cs or -EvitaDB.Client/Models/Data/IBuilder.cs can be -passed to EvitaDB.Client/EvitaClientSession.cs `Upsert` method, -which returns EvitaDB.Client/Models/Data/Structure/EntityReference.cs -containing only entity type and (possibly assigned) primary key information. You can also use the `UpsertAndFetchEntity` -method, which inserts or creates the entity and returns its body in the form and size you specify in your `Require` -argument. + #### Creating entities in detached mode -Entity instances can be created even if no EvitaDB instance is available: +Entity instances can be created even if no evitaDB instance is available: -[Detached instantiation example](/documentation/user/en/use/api/example/detached-instantiation.cs) +[Detached instantiation example](/documentation/user/en/use/api/example/detached-instantiation.java) Although you will probably only take advantage of this approach when writing test cases, it still allows you to create @@ -830,17 +688,19 @@ a stream of mutations that can be sent to and processed by the evitaDB server on There is an analogous builder that takes an existing entity and tracks changes made to it. - + [Detached existing entity example](/documentation/user/en/use/api/example/detached-existing-entity-instantiation.java) - -In the GraphQL API, there is no way to send full entity object to the server to be stored. Instead, you send a collection -of mutations that add, change, or remove individual data from an entity (new or existing one). Similarly to how the schema -is defined in the GraphQL API. + + +In the GraphQLREST API, +there is no way to send full entity object to the server to be stored. Instead, you send a collection +of mutations that add, change, or remove individual data from an entity (new or existing one). Similarly to how the schema +is defined in the GraphQLREST API. @@ -852,11 +712,14 @@ is defined in the GraphQL API. We know that this approach is not very user-friendly. However, the idea behind this approach is to provide a simple and versatile way to programmatically build an entity with transactions in mind (in fact, this is how evitaDB works internally, so the collection of mutations is passed directly to the engine on the server). It is expected that the developer -using the GraphQL API will create a library with e.g. entity builders that will generate the collection of mutations for -the entity definition (see Java API for inspiration). +using the GraphQLREST API +will create a library with e.g. entity builders that will generate the collection of mutations for the entity definition +(see Java API for inspiration). + + You can create a new entity or update an existing one using the [catalog data API](/documentation/user/en/use/connectors/graphql.md#graphql-api-instances) at the `https://your-server:5555/gql/test-catalog` URL. This API contains `upsertCollectionName` GraphQL mutations for each [entity collection](/documentation/user/en/use/data-model.md#collection) that are customized to collections' @@ -864,62 +727,31 @@ at the `https://your-server:5555/gql/test-catalog` URL. This API contains `upser the changes to be applied to an entity. In one go, you can then retrieve the entity with the changes applied by defining return data. - - -[Creating new entity example](/documentation/user/en/use/api/example/create-new-entity.graphql) - - -Because these GraphQL mutations are also for updating existing entities, evitaDB will automatically -either create a new entity with specified mutations (and possibly a primary key) or update an existing one if a primary key -of an existing entity is specified. You can further customize the behavior of the mutation by specifying the `entityExistence` -argument. - - - -[Updating existing entity example](/documentation/user/en/use/api/example/update-existing-entity.graphql) - - -In the REST API, there is no way to send full entity object to the server to be stored. Instead, you send a collection -of mutations that add, change, or remove individual data from an entity (new or existing one). Similarly to how the schema -is defined in the REST API. - - - - - -##### Why do we use the mutation approach for entity definition? - - -We know that this approach is not very user-friendly. However, the idea behind this approach is to provide a simple and versatile -way to programmatically build an entity with transactions in mind (in fact, this is how evitaDB works internally, -so the collection of mutations is passed directly to the engine on the server). It is expected that the developer -using the REST API will create a library with e.g. entity builders that will generate the collection of mutations for -the entity definition (see Java API for inspiration). - - - You can create a new entity or update an existing one using the [catalog API](/documentation/user/en/use/connectors/rest.md#rest-api-instances) -at a collection endpoint, for example `https://your-server:5555/test/test-catalog/product` with `PUT` HTTP method. -There endpoints are customized to collections' [schemas](/documentation/user/en/use/schema.md#entity). These endpoints take a -collection of evitaDB mutations which define the changes to be applied to an entity. In one go, you can then retrieve the +at a collection endpoint, for example `https://your-server:5555/test/test-catalog/product` with `PUT` HTTP method. +There endpoints are customized to collections' [schemas](/documentation/user/en/use/schema.md#entity). These endpoints take a +collection of evitaDB mutations which define the changes to be applied to an entity. In one go, you can then retrieve the entity with the changes applied by defining requirements. + + -[Creating new entity example](/documentation/user/en/use/api/example/create-new-entity.rest) +[Creating new entity example](/documentation/user/en/use/api/example/create-new-entity.graphql) -Because these endpoints are also for updating existing entities, evitaDB will automatically +Because these GraphQL mutationsendpoints +are also for updating existing entities, evitaDB will automatically either create a new entity with specified mutations (and possibly a primary key) or update an existing one if a primary key of an existing entity is specified. You can further customize the behavior of the mutation by specifying the `entityExistence` argument. -[Updating existing entity example](/documentation/user/en/use/api/example/update-existing-entity.rest) +[Updating existing entity example](/documentation/user/en/use/api/example/update-existing-entity.graphql) @@ -958,43 +790,27 @@ If you want to return bodies of deleted entities, you can use alternative method - - -The delete mutation can return entity bodies, so you can define the return structure of data as you need as if you were fetching -entities in usual way. - - - -evitaDB may not remove all entities matched by the filter part of the query. The removal of entities is subject to the -logic of the pagination arguments `offset` and `limit`. Even if you omit the -these arguments completely, implicit pagination (`offset: 1, limit: 20`) will be used. If the number of entities removed is equal to the -size of the defined paging, you should repeat the removal command. - -Massive entity removal is better to execute in multiple transactional rounds rather than in one big transaction, i.e. multiple -GraphQL requests. This is at least a -good practice, because large and long-running transactions increase probability of conflicts that lead to -rollbacks of other transactions. - - - - - + -Both deletion endpoints can return entity bodies, so you can define the return requirements as you need as if you were fetching -entities in usual way. +The delete mutationBoth deletion endpoints +can return entity bodies, so you can define the return structure of data as you need as if you were fetching entities in +usual way. - + -evitaDB may not remove all entities matched by the filter part of the query. The removal of entities is subject to the -logic of the `require` conditions [`page` or `strip`](../../query/requirements/paging.md). Even if you omit the -`require` part completely, implicit pagination (`page(1, 20)`) will be used. If the number of entities removed is equal to the -size of the defined paging, you should repeat the removal command. -Massive entity removal is better to execute in multiple transactional rounds rather than in one big transaction. This is -at least a good practice, because large and long-running transactions increase the probability of conflicts that lead to +evitaDB may not remove all entities matched by the filter part of the query. The removal of entities is subject to the +logic of the `require` conditions [`page` or `strip`](../../query/requirements/paging.md) +pagination arguments [`offset` and `limit`](../../query/requirements/paging.md). +Even if you omit the these completely, implicit pagination (`page(1, 20)`) +(`offset: 1, limit: 20`) will be used. If the number of entities removed +is equal to the size of the defined paging, you should repeat the removal command. + +Massive entity removal is better to execute in multiple transactional rounds rather than in one big transaction, i.e. multiple requests. +This is at least a good practice, because large and long-running transactions increase probability of conflicts that lead to rollbacks of other transactions. @@ -1022,25 +838,19 @@ If you remove only the root node without removing its children, the children wil ##### How does evitaDB handle the removals internally? -No data is actually removed once it is created and stored. If you remove the reference/attribute/whatever, it remains +No data is actually removed once it is created and stored. If you remove the reference/attribute/whatever, it remains in the entity and is just marked as `dropped`. See the -evita_api/src/main/java/io/evitadb/api/requestResponse/data/Droppable.javaEvitaDB.Client/Models/Data/IDroppable.cs interface -implementations. +evita_api/src/main/java/io/evitadb/api/requestResponse/data/Droppable.java +EvitaDB.Client/Models/Data/IDroppable.cs +interface implementations. There are a few reasons for this decision: -1. it's good to have the last known version of the data around when things go wrong, so we can still recover to the +1. it's good to have the last known version of the data around when things go wrong, so we can still recover to the previous state. 2. it allows us to track the changes in the entity through its lifecycle for debugging purposes 3. it is consistent with our *append-only* storage approach where we need to write [tombstones](https://en.wikipedia.org/wiki/Tombstone_(data_store)) in case of entity or other object removals - - - - -Unfortunately, it is currently not possible to write data using EvitaQL. This extension is also not planned to be -implemented in the near future, because we believe that sufficient options (Java, GraphQL, REST API, gRPC and C#) are available. - \ No newline at end of file From d666be22a2cc5e731633bd4ac3fe09deebbf327d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Tue, 12 Dec 2023 08:49:02 +0100 Subject: [PATCH 25/52] docs: write-data refactor --- documentation/user/en/use/api/write-data.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/documentation/user/en/use/api/write-data.md b/documentation/user/en/use/api/write-data.md index a0c5e4ad7..b781ad830 100644 --- a/documentation/user/en/use/api/write-data.md +++ b/documentation/user/en/use/api/write-data.md @@ -374,8 +374,8 @@ interface, so you can use them this way: [Get advantage of Autocloseable behaviour](/documentation/user/en/use/api/example/autocloseable-transaction-management.java) -This approach is safe, but has the same disadvantage as using `queryCatalog` / `updateCatalog` -`QueryCatalog` / `UpdateCatalog` methods - you need to +This approach is safe, but has the same disadvantage as using `queryCatalog` / `updateCatalog` +`QueryCatalog` / `UpdateCatalog` methods - you need to have all the business logic executable within the same block. #### Dry-run session @@ -447,7 +447,7 @@ see, that you can call on it either:
- +
`BuildChangeSet`
@@ -458,7 +458,7 @@ see, that you can call on it either: from the server again, you'll see that none of the changes have been applied to the database entity.
- +
From 6a496c9c8df76f797f07f043ca851c45a1fe332e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Tue, 12 Dec 2023 10:05:25 +0100 Subject: [PATCH 26/52] docs: write-data refactor --- documentation/user/en/use/api/schema-api.md | 62 ++++++++++----------- documentation/user/en/use/api/write-data.md | 23 ++++---- 2 files changed, 44 insertions(+), 41 deletions(-) diff --git a/documentation/user/en/use/api/schema-api.md b/documentation/user/en/use/api/schema-api.md index 0b55767c8..86a2a1eaf 100644 --- a/documentation/user/en/use/api/schema-api.md +++ b/documentation/user/en/use/api/schema-api.md @@ -1,7 +1,7 @@ --- title: Schema API perex: | - Currently, you can define the schema using the Java, C#, REST, and GraphQL APIs. All three approaches are covered in + Currently, you can define the schema using the Java, C#, REST, and GraphQL APIs. All three approaches are covered in this chapter. date: '17.1.2023' author: 'Ing. Jan Novotný' @@ -49,62 +49,62 @@ The model is expected to be annotated with following annotations:
evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/PrimaryKey.java
- Annotation can be placed on [int](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html) + Annotation can be placed on [int](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html) field / getter method / record component and marks an entity [primary key](#primary-key-generation).
evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/Attribute.java
Annotation can be placed on field / getter method / record component and marks an entity [attribute](#attribute). - Default values in case of interfaces can be provided using default method implementation (see the example + Default values in case of interfaces can be provided using default method implementation (see the example below).
evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/AssociatedData.java
- Annotation can be placed on field / getter method / record component and marks an entity + Annotation can be placed on field / getter method / record component and marks an entity [associated data](#associated-data).
evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/ParentEntity.java
- Annotation can be placed on field / getter method / record component and marks a reference to another entity - that represents the hierarchical parent for this entity. The model class should be the same as the entity class + Annotation can be placed on field / getter method / record component and marks a reference to another entity + that represents the hierarchical parent for this entity. The model class should be the same as the entity class (see `@Entity` annotation).
evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/Price.java
Annotation can be placed on field / getter method / record component of collection / array of type evita_api/src/main/java/io/evitadb/api/requestResponse/data/PriceContract.java - that provides access to all prices of the entity. Using this annotation in the entity model class enables + that provides access to all prices of the entity. Using this annotation in the entity model class enables [prices](#prices) in the entity schema.
evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/PriceForSale.java
- Annotation can be placed on field / getter method / record component of type - evita_api/src/main/java/io/evitadb/api/requestResponse/data/PriceContract.java - that provides access to price for sale of the entity. Using this annotation in the entity model class enables + Annotation can be placed on field / getter method / record component of type + evita_api/src/main/java/io/evitadb/api/requestResponse/data/PriceContract.java + that provides access to price for sale of the entity. Using this annotation in the entity model class enables [prices](#prices) in the entity schema.
evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/Reference.java
- Annotation can be placed on field / getter method / record component and marks an entity as a - [reference](#reference) to another entity. It can point to another model class (interface/class/record) - that contains properties for `@ReferencedEntity` and @ReferencedEntityGroup` annotations and relation + Annotation can be placed on field / getter method / record component and marks an entity as a + [reference](#reference) to another entity. It can point to another model class (interface/class/record) + that contains properties for `@ReferencedEntity` and `@ReferencedEntityGroup` annotations and relation attributes.
evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/ReferencedEntity.java
- Annotation can be placed on field / getter method / record component and marks a reference to another entity - that represents the referenced entity for this entity. The model class should represent a entity class model + Annotation can be placed on field / getter method / record component and marks a reference to another entity + that represents the referenced entity for this entity. The model class should represent a entity class model (see `@Entity` annotation).
evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/ReferencedEntityGroup.java
- Annotation can be placed on field / getter method / record component and marks a reference to another entity - that represents the referenced entity group for this entity. The model class should represent a entity class + Annotation can be placed on field / getter method / record component and marks a reference to another entity + that represents the referenced entity group for this entity. The model class should represent a entity class model (see `@Entity` annotation).
-Methods / fields / record components that are not annotated are ignored during schema definition. For a better idea, +Methods / fields / record components that are not annotated are ignored during schema definition. For a better idea, let's demonstrate a sample of the interface design of the product entity. @@ -116,9 +116,9 @@ let's demonstrate a sample of the interface design of the product entity. You can also use the contract for the schema definition in the [query API](./query-data.md) as an expected result type -and evitaDB will automatically generate an appropriate proxy class that maps the generic underlying data structure -to the contract of your imagination. You can find more information on this topic in -the [Java Connector chapter](../connectors/java.md#custom-contracts). +and evitaDB will automatically generate an appropriate proxy class that maps the generic underlying data structure +to the contract of your imagination. You can find more information on this topic in +the [Java Connector chapter](../connectors/java.md#custom-contracts). @@ -128,7 +128,7 @@ the [Java Connector chapter](../connectors/java.md#custom-contracts). Unlike the Java approach, the GraphQL API supports only an imperative schema definition. The schema is defined using atomic mutations where each mutation adds, changes or removes a small part of the entire schema. To define an entire schema, -you typically need to pass a collection of multiple mutations. +you typically need to pass a collection of multiple mutations. @@ -138,14 +138,14 @@ you typically need to pass a collection of multiple mutations. We know that this approach is not very user-friendly. However, the idea behind this approach is to provide a simple and versatile -way to programmatically define a schema (in fact, this is how evitaDB works internally, -so the collection of mutations is passed directly to the engine on the server). It is expected that the developer -using the GraphQL API will create a library with e.g. entity schema builders that will generate the collection of mutations for +way to programmatically define a schema (in fact, this is how evitaDB works internally, +so the collection of mutations is passed directly to the engine on the server). It is expected that the developer +using the GraphQL API will create a library with e.g. entity schema builders that will generate the collection of mutations for the schema definition. -You can define a new catalog schema or update an existing one using the +You can define a new catalog schema or update an existing one using the [catalog schema API](/documentation/user/en/use/connectors/graphql.md#graphql-api-instances) at the `https://your-server:5555/gql/test-catalog/schema` URL: @@ -193,7 +193,7 @@ at the `https://your-server:5555/rest/test-catalog/schema` URL: [Imperative catalog schema definition via REST API](/documentation/user/en/use/api/example/imperative-catalog-schema-definition.rest) -or update the schema of a specific entity collection at e.g. an `https://your-server:5555/rest/test-catalog/product/schema` URL +or update the schema of a specific entity collection at e.g. an `https://your-server:5555/rest/test-catalog/product/schema` URL for the collection `Product` using a REST mutation of the selected collection like this: @@ -205,9 +205,9 @@ for the collection `Product` using a REST mutation of the selected collection li -Unlike the Java approach, the C# client supports only an imperative schema definition. -The schema is defined using builder pattern, that is provided by EvitaDB.Client/Models/Schemas/IEntitySchemaBuilder.cs interface. -Behind the scenes, instance of such builder is converted to the collection of mutations, that are sent to the server. +Unlike the Java approach, the C# client supports only an imperative schema definition. +The schema is defined using builder pattern, that is provided by EvitaDB.Client/Models/Schemas/IEntitySchemaBuilder.cs interface. +Behind the scenes, instance of such builder is converted to the collection of mutations, that are sent to the server. ## Imperative schema definition @@ -222,6 +222,6 @@ A schema can be programmatically defined this way: Unfortunately, it is currently not possible to define a schema using EvitaQL. This extension is also not planned to be -implemented in the near future, because we believe that sufficient options (Java, GraphQL, REST API) are available +implemented in the near future, because we believe that sufficient options (Java, GraphQL, REST API) are available for schema definition. \ No newline at end of file diff --git a/documentation/user/en/use/api/write-data.md b/documentation/user/en/use/api/write-data.md index b781ad830..0c6486e89 100644 --- a/documentation/user/en/use/api/write-data.md +++ b/documentation/user/en/use/api/write-data.md @@ -91,7 +91,8 @@ the primary data store. One of the more interesting recent developments in this [the Debezium project](https://debezium.io/), which allows changes from primary data stores to be streamed to secondary indexes fairly easily. -There might be multiple clients reading & writing data to the same catalog when it is in `ALIVE` state. Each catalog +There might be multiple clients reading & writing data to the same catalog when it is in `ALIVE` +`Alive` state. Each catalog update is wrapped into a *transaction* that meets [the snapshot isolation level](https://en.wikipedia.org/wiki/Snapshot_isolation). More details about transaction handling is in [separate chapter](../../deep-dive/transactions.md). @@ -125,7 +126,7 @@ Interfaces follow this structure: When you create new entity using evitaDB API, you obtain a builder, and you can immediately start setting the data to the entity and then store the entity to the database: - + [Creating new entity returns a builder](/documentation/user/en/use/api/example/create-new-entity-shortened.java) @@ -136,7 +137,7 @@ basically a contract interface with a few methods allowing you to convert it to for updating the data: - + [Retrieving existing entity returns a sealed entity](/documentation/user/en/use/api/example/update-existing-entity-shortened.java) @@ -428,7 +429,7 @@ the builder wrapper), modify it, and finally collect the changes and send them t -The `upsertVia``UpsertVia +The `upsertVia``UpsertVia` method is a shortcut for calling `session.upsertEntity(builder.buildChangeSet())` `session.UpsertEntity(builder.BuildChangeSet())`. If you look at the evita_api/src/main/java/io/evitadb/api/requestResponse/data/BuilderContract.java @@ -803,11 +804,12 @@ usual way. evitaDB may not remove all entities matched by the filter part of the query. The removal of entities is subject to the -logic of the `require` conditions [`page` or `strip`](../../query/requirements/paging.md) -pagination arguments [`offset` and `limit`](../../query/requirements/paging.md). -Even if you omit the these completely, implicit pagination (`page(1, 20)`) -(`offset: 1, limit: 20`) will be used. If the number of entities removed -is equal to the size of the defined paging, you should repeat the removal command. +logic of the `require` conditions [`page` or `strip`](../../query/requirements/paging.md) +`Require` conditions [`Page` or `Strip`](../../query/requirements/paging.md) +pagination arguments [`offset` and `limit`](../../query/requirements/paging.md). +Even if you omit the these completely, implicit pagination (`page(1, 20)`) +(`Page(1, 20)`)(`offset: 1, limit: 20`) +will be used. If the number of entities removed is equal to the size of the defined paging, you should repeat the removal command. Massive entity removal is better to execute in multiple transactional rounds rather than in one big transaction, i.e. multiple requests. This is at least a good practice, because large and long-running transactions increase probability of conflicts that lead to @@ -820,7 +822,8 @@ rollbacks of other transactions. If you are removing a hierarchical entity, and you need to remove not only the entity itself, but its entire subtree, -you can take advantage of `deleteEntityAndItsHierarchy``DeleteEntityAndItsHierarchy` method. +you can take advantage of `deleteEntityAndItsHierarchy` +`DeleteEntityAndItsHierarchy` method. By default, the method returns the number of entities removed, but alternatively it can return the body of the removed root entity with the size and form you specify in its `require``Require` argument. From e7d97f5cbc5f15d4fc752e75681ccde88550b490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Tue, 12 Dec 2023 10:07:11 +0100 Subject: [PATCH 27/52] docs: write-data refactor --- documentation/user/en/use/api/write-data.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/user/en/use/api/write-data.md b/documentation/user/en/use/api/write-data.md index 0c6486e89..738ecaf85 100644 --- a/documentation/user/en/use/api/write-data.md +++ b/documentation/user/en/use/api/write-data.md @@ -67,7 +67,7 @@ sessions in parallel to this particular catalog. Any newly created catalog starts in `WARMUP` state and must be manually switched to *transactional* mode using the -[system API](/documentation/user/en/use/connectors/graphql.md#graphql-api-instances)(/documentation/user/en/use/connectors/rest.md#rest-api-instances) +[system API](/documentation/user/en/use/connectors/graphql.md#graphql-api-instances)[system API](/documentation/user/en/use/connectors/rest.md#rest-api-instances) by executing: From ac4e8ed906f73b5873b584b8eaac48d09aaf3ef0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Tue, 12 Dec 2023 10:08:15 +0100 Subject: [PATCH 28/52] docs: write-data refactor --- documentation/user/en/use/api/write-data.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/documentation/user/en/use/api/write-data.md b/documentation/user/en/use/api/write-data.md index 738ecaf85..ccb1447a8 100644 --- a/documentation/user/en/use/api/write-data.md +++ b/documentation/user/en/use/api/write-data.md @@ -67,7 +67,8 @@ sessions in parallel to this particular catalog. Any newly created catalog starts in `WARMUP` state and must be manually switched to *transactional* mode using the -[system API](/documentation/user/en/use/connectors/graphql.md#graphql-api-instances)[system API](/documentation/user/en/use/connectors/rest.md#rest-api-instances) +[system API](/documentation/user/en/use/connectors/graphql.md#graphql-api-instances) +[system API](/documentation/user/en/use/connectors/rest.md#rest-api-instances) by executing: From ba2629226a75b2013b90f8213fede835e1fb36db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Tue, 12 Dec 2023 10:35:22 +0100 Subject: [PATCH 29/52] docs: REST connection API url patterns --- .../user/en/use/connectors/graphql.md | 67 ++++++++++--------- documentation/user/en/use/connectors/rest.md | 42 ++++++------ 2 files changed, 58 insertions(+), 51 deletions(-) diff --git a/documentation/user/en/use/connectors/graphql.md b/documentation/user/en/use/connectors/graphql.md index 4adebc231..092a8e736 100644 --- a/documentation/user/en/use/connectors/graphql.md +++ b/documentation/user/en/use/connectors/graphql.md @@ -1,9 +1,9 @@ --- title: GraphQL perex: | - GraphQL is an open-source data query and manipulation language for APIs, providing a powerful alternative to REST by - allowing clients to specify exactly what data they need, reducing over-fetching and under-fetching of data. Developed - by Facebook in 2012 and open-sourced in 2015, it enables declarative data fetching where the client can ask for what + GraphQL is an open-source data query and manipulation language for APIs, providing a powerful alternative to REST by + allowing clients to specify exactly what data they need, reducing over-fetching and under-fetching of data. Developed + by Facebook in 2012 and open-sourced in 2015, it enables declarative data fetching where the client can ask for what it needs and get exactly that. date: '21.3.2023' author: 'Lukáš Hornych' @@ -35,34 +35,37 @@ ones.

Used terms

-
-
catalog data API
-
-

A GraphQL API instance that provides ways to query and update actual data (typically entities and related data) - of a single catalog.

-

URL pattern: `/gql/{catalog name}`

-
-
catalog schema API
-
-

A GraphQL API instance that provides ways to fetch and modify the internal structure of a single catalog.

-

URL pattern: `/gql/{catalog name}/schema`

-
-
system API
-
-

A GraphQL API instance that provides ways to manage evitaDB itself.

-

URL pattern: `/gql/system`

-
-
+
+
catalog data API
+
+ A GraphQL API instance that provides ways to query and update actual data (typically entities and related data) + of a single catalog. + + URL pattern: `/gql/{catalog-name}` +
+
catalog schema API
+
+ A GraphQL API instance that provides ways to fetch and modify the internal structure of a single catalog. + + URL pattern: `/gql/{catalog-name}/schema` +
+
system API
+
+ A GraphQL API instance that provides ways to manage evitaDB itself. + + URL pattern: `/gql/system` +
+
There isn't a single GraphQL API instance for the whole evitaDB instance. Instead, each evitaDB [catalog](/documentation/user/en/use/data-model.md#catalog) -has its own GraphQL API (actually each catalog has two GraphQL -API instances, but more on that later) on its own URL with data only from that particular catalog. +has its own GraphQL API (actually each catalog has two GraphQL +API instances, but more on that later) on its own URL with data only from that particular catalog. In addition, there is one another GraphQL API instance that is reserved for evitaDB administration (e.g. creating new catalogs, removing existing catalogs) called system API. -Each GraphQL API instance URL starts with `/gql`, followed by the URL-formatted catalog name for specific -catalog APIs, or with the reserved `system` keyword for the above-mentioned system API. For each catalog there is a +Each GraphQL API instance URL starts with `/gql`, followed by the URL-formatted catalog name for specific +catalog APIs, or with the reserved `system` keyword for the above-mentioned system API. For each catalog there is a catalog data API located at the `/gql/{catalog name}` base URL and a catalog schema API located at the `/gql/{catalog name}/schema` URL. The catalog data API is used to retrieve and update the actual data of a given catalog. The catalog schema API is more of an "introspection" API, as it allows users to view and modify internal evitaDB @@ -73,7 +76,7 @@ Each GraphQL API instance supports only `POST` HTTP method for executing queries ### GraphQL schema fetching Each GraphQL API instance supports standard [introspection capabilities](https://graphql.org/learn/introspection/) for GraphQL schema -reconstruction. On top of that, each instance supports fetching reconstructed GraphQL schema in DSL format using +reconstruction. On top of that, each instance supports fetching reconstructed GraphQL schema in DSL format using `GET` HTTP request on the instance URL. For example, to fetch the GraphQL schema of the `fashion` catalog, you can issue the following HTTP request: ```http request @@ -116,7 +119,7 @@ with its own relevant GraphQL schema: ### Structure of catalog data APIs -A single catalog data API for a single catalog contains only a few types of queries and mutations, but most of them are "duplicated" for +A single catalog data API for a single catalog contains only a few types of queries and mutations, but most of them are "duplicated" for each [collection](/documentation/user/en/use/data-model.md#collection) within that catalog. Each query or mutation then takes arguments and returns data specific to a given collection and [its schema](/documentation/user/en/use/schema.md#entity). @@ -137,15 +140,15 @@ There is nothing special about the system API, just a set of basic ## Query language evitaDB is shipped with its own [query language](/documentation/user/en/query/basics.md), for which our GraphQL API has its own facade. -The main difference between these two is that the evitaDB's original language has generic set of constraints and doesn't -care about concrete [collection](/documentation/user/en/use/data-model.md#collection) data structure, where the +The main difference between these two is that the evitaDB's original language has generic set of constraints and doesn't +care about concrete [collection](/documentation/user/en/use/data-model.md#collection) data structure, where the GraphQL version has the same constraints but customized based on [collection](/documentation/user/en/use/data-model.md#collection) data structure to provide concrete available constraint for defined data structure. This custom version of the query language is possible because in our GraphQL API, the query language schema is dynamically generated -based on [internal collection schemas](/documentation/user/en/use/schema#entity) to display only constraints that -can actually be used to query data (which also changes based on context of nested constraints). This also provides constraint arguments with data types that match -the internal data. This helps with the self-documentation because you don't necessarily need to know about +based on [internal collection schemas](/documentation/user/en/use/schema#entity) to display only constraints that +can actually be used to query data (which also changes based on context of nested constraints). This also provides constraint arguments with data types that match +the internal data. This helps with the self-documentation because you don't necessarily need to know about the domain model, since most of GraphQL IDEs will auto-complete the available constraints from the GraphQL API schema. ### Syntax of query and constraints diff --git a/documentation/user/en/use/connectors/rest.md b/documentation/user/en/use/connectors/rest.md index ab8829a41..581c6e6e5 100644 --- a/documentation/user/en/use/connectors/rest.md +++ b/documentation/user/en/use/connectors/rest.md @@ -1,8 +1,8 @@ --- title: REST perex: | - The Representational State Transfer (REST) API protocol is a standardized approach to building web services that - employ HTTP methods to create, read, update, and delete data. The protocol is designed around resources, which are any + The Representational State Transfer (REST) API protocol is a standardized approach to building web services that + employ HTTP methods to create, read, update, and delete data. The protocol is designed around resources, which are any kind of object, data, or service that can be accessed by the client. Its simplicity, scalability, and performance make REST the most popular protocol for APIs, used extensively in cloud services, mobile services, and social networks. date: '24.3.2023' @@ -11,7 +11,7 @@ preferredLang: 'rest' --- The [REST](https://restfulapi.net/) API with an [OpenAPI schema](https://swagger.io/specification/v3/) in evitaDB has -been developed to allow users and developers to easily query domain-specific data from evitaDB via universal well-known +been developed to allow users and developers to easily query domain-specific data from evitaDB via universal well-known API standard that REST APIs provide. The main idea behind our REST API implementation is that the [OpenAPI schema](https://swagger.io/specification/v3/) is dynamically generated based on @@ -24,17 +24,21 @@ equivalent to the ones specified in evitaDB instead of some generic ones.

Used terms

-
-
catalog API
-
- A REST API instance that provides ways to query and update actual data (typically entities and related data) - of a single catalog, as well as ways to fetch and modify the internal structure of a single catalog. -
-
system API
-
- A REST API instance that provides ways to manage evitaDB itself. -
-
+
+
catalog API
+
+ A REST API instance that provides ways to query and update actual data (typically entities and related data) + of a single catalog, as well as ways to fetch and modify the internal structure of a single catalog. + + URL pattern: `/rest/{catalog-name}` +
+
system API
+
+ A REST API instance that provides ways to manage evitaDB itself. + + URL pattern: `/rest/system` +
+
There isn't a single REST API instance for the whole evitaDB instance. Instead, each evitaDB [catalog](/documentation/user/en/use/data-model.md#catalog) @@ -43,12 +47,12 @@ In addition, there is one another REST API instance that is reserved for evitaDB (e.g., creating new catalogs, removing existing catalogs) called system API. Each REST API instance URL starts with `/rest`, followed by the URL-formatted catalog name for specific -catalog APIs, or with the reserved `system` keyword for the above-mentioned system API. +catalog APIs, or with the reserved `system` keyword for the above-mentioned system API. You can download [OpenAPI schema](https://swagger.io/specification/v3/) of each REST API instance by calling `GET` -HTTP request to that base URL which then lists all available endpoints. +HTTP request to that base URL which then lists all available endpoints. URLs of these REST API instances with above-mentioned base URLs are then further suffixed with specific resources. -In case of system API those are typically catalogs. In case of catalog API +In case of system API those are typically catalogs. In case of catalog API there are resources for individual [collections](/documentation/user/en/use/data-model.md#collection) and their actions. @@ -114,8 +118,8 @@ standard tools. However, below are our recommendations for tools etc. that we us ### Recommended IDEs For a desktop IDE to test and explore the REST APIs you can use [Insomnia](https://insomnia.rest/) or [Postman](https://www.postman.com/). -For generating documentation from the [OpenAPI schema](https://swagger.io/specification/v3/) we recommend either -[Redocly](https://redocly.com/docs/cli/commands/preview-docs/) tool or official [Swagger Editor](https://github.com/swagger-api/swagger-editor) +For generating documentation from the [OpenAPI schema](https://swagger.io/specification/v3/) we recommend either +[Redocly](https://redocly.com/docs/cli/commands/preview-docs/) tool or official [Swagger Editor](https://github.com/swagger-api/swagger-editor) which can even generate clients for your programming language. ### Recommended client libraries From 927daa0432c4cdf865947954ed30f21140575128 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Tue, 12 Dec 2023 10:52:35 +0100 Subject: [PATCH 30/52] docs(#200): fixes after merge --- documentation/user/en/use/api/write-data.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/user/en/use/api/write-data.md b/documentation/user/en/use/api/write-data.md index 95ea3f3e6..ba98a23f1 100644 --- a/documentation/user/en/use/api/write-data.md +++ b/documentation/user/en/use/api/write-data.md @@ -127,7 +127,7 @@ Interfaces follow this structure: When you create new entity using evitaDB API, you obtain a builder, and you can immediately start setting the data to the entity and then store the entity to the database: - + [Creating new entity returns a builder](/documentation/user/en/use/api/example/create-new-entity-shortened.java) @@ -138,7 +138,7 @@ basically a contract interface with a few methods allowing you to convert it to for updating the data: - + [Retrieving existing entity returns a sealed entity](/documentation/user/en/use/api/example/update-existing-entity-shortened.java) @@ -740,7 +740,7 @@ entity with the changes applied by defining requirements. - + [Creating new entity example](/documentation/user/en/use/api/example/create-new-entity.graphql) From 816c603c9a613d03bc4acbe34d28cdaa33445040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Tue, 12 Dec 2023 11:16:04 +0100 Subject: [PATCH 31/52] docs(#200): fix code blocks --- documentation/user/en/use/api/write-data.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/documentation/user/en/use/api/write-data.md b/documentation/user/en/use/api/write-data.md index ba98a23f1..fe2e463d9 100644 --- a/documentation/user/en/use/api/write-data.md +++ b/documentation/user/en/use/api/write-data.md @@ -355,7 +355,7 @@ the lambda function. That's why there is an alternative - not so secure - approach to handling sessions and transactions: - + [Manual session and transaction handling](/documentation/user/en/use/api/example/manual-transaction-management.java) @@ -371,7 +371,7 @@ Both evita_api/src/main/java/io/evitadb `Autocloseable`EvitaDB.Client/EvitaClientTransaction.cs implement C# `IDisposable` interface, so you can use them this way: - + [Get advantage of Autocloseable behaviour](/documentation/user/en/use/api/example/autocloseable-transaction-management.java) @@ -384,7 +384,7 @@ have all the business logic executable within the same block. For testing purposes, there is a special flag that can be used when opening a new session - a **dry run** flag: - + [Opening dry-run session](/documentation/user/en/use/api/example/dry-run-session.java) @@ -681,7 +681,7 @@ the reference is automatically removed. Entity instances can be created even if no evitaDB instance is available: - + [Detached instantiation example](/documentation/user/en/use/api/example/detached-instantiation.java) @@ -690,7 +690,7 @@ a stream of mutations that can be sent to and processed by the evitaDB server on There is an analogous builder that takes an existing entity and tracks changes made to it. - + [Detached existing entity example](/documentation/user/en/use/api/example/detached-existing-entity-instantiation.java) From 956f051061c0d119ac86852bb1a6d8a0607c8a92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Novotn=C3=BD?= Date: Tue, 12 Dec 2023 15:20:24 +0100 Subject: [PATCH 32/52] docs: finalized write API documentation with tests Also corrected problems that were revealed by the tests. --- .../example/declarative-model-example.java | 2 +- .../examples/custom-contract-reading.java | 32 ++++--- .../examples/custom-contract-writing.java | 40 +++++++-- .../examples/instance-editor-example.java | 33 ++++--- .../examples/sealed-instance-example.java | 21 +++-- .../sealed-open-lifecycle-example.java | 2 +- .../examples/selective-imports.java | 2 + documentation/user/en/use/connectors/java.md | 10 +-- .../api/proxy/impl/ProxycianFactory.java | 24 +++-- .../GetAssociatedDataMethodClassifier.java | 7 +- .../entity/GetAttributeMethodClassifier.java | 6 +- .../entity/GetReferenceMethodClassifier.java | 6 +- ...GetReferenceAttributeMethodClassifier.java | 6 +- .../schema/ClassSchemaAnalyzer.java | 9 +- .../io/evitadb/utils/ReflectionLookup.java | 37 ++++++++ .../documentation/UserDocumentationTest.java | 2 +- .../evitadb/documentation/mock/Product.java | 90 +++++++++++++++++++ .../documentation/mock/ProductEditor.java | 54 +++++++++++ .../META-INF/documentation/imports.java | 9 +- 19 files changed, 326 insertions(+), 66 deletions(-) create mode 100644 documentation/user/en/use/connectors/examples/selective-imports.java create mode 100644 evita_functional_tests/src/test/java/io/evitadb/documentation/mock/Product.java create mode 100644 evita_functional_tests/src/test/java/io/evitadb/documentation/mock/ProductEditor.java diff --git a/documentation/user/en/use/api/example/declarative-model-example.java b/documentation/user/en/use/api/example/declarative-model-example.java index ec0692610..704988253 100644 --- a/documentation/user/en/use/api/example/declarative-model-example.java +++ b/documentation/user/en/use/api/example/declarative-model-example.java @@ -4,7 +4,7 @@ EvolutionMode.ADDING_CURRENCIES } ) -public interface Product { +public interface Product extends Serializable { @PrimaryKey int getId(); diff --git a/documentation/user/en/use/connectors/examples/custom-contract-reading.java b/documentation/user/en/use/connectors/examples/custom-contract-reading.java index 8f862d05f..5e44202bf 100644 --- a/documentation/user/en/use/connectors/examples/custom-contract-reading.java +++ b/documentation/user/en/use/connectors/examples/custom-contract-reading.java @@ -7,7 +7,7 @@ ); // get single product by specific query - final Optional product = session.queryOne( + final Optional optionalProduct = session.queryOne( query( filterBy( attributeEquals("code", "macbook-pro-13") @@ -16,17 +16,19 @@ entityFetchAll() ) ), - ProductInterface.class + Product.class ); // get multiple products in category - final List products = session.queryList( + final List products = session.queryList( query( filterBy( - hierarchyWithin( - "categories", - filterBy( - attributeEquals("code", "laptops") + referenceHaving( + "marketingBrand", + entityHaving( + filterBy( + attributeEquals("code", "sony") + ) ) ) ), @@ -34,17 +36,19 @@ entityFetchAll() ) ), - ProductInterface.class + Product.class ); // or finally get page of products in category - final EvitaResponse productResponse = session.query( + final EvitaResponse productResponse = session.query( query( filterBy( - hierarchyWithin( - "categories", - filterBy( - attributeEquals("code", "laptops") + referenceHaving( + "marketingBrand", + entityHaving( + filterBy( + attributeEquals("code", "sony") + ) ) ) ), @@ -52,7 +56,7 @@ entityFetchAll() ) ), - ProductInterface.class + Product.class ); } ); \ No newline at end of file diff --git a/documentation/user/en/use/connectors/examples/custom-contract-writing.java b/documentation/user/en/use/connectors/examples/custom-contract-writing.java index eb387ed8c..552033124 100644 --- a/documentation/user/en/use/connectors/examples/custom-contract-writing.java +++ b/documentation/user/en/use/connectors/examples/custom-contract-writing.java @@ -1,16 +1,40 @@ evita.updateCatalog( "evita", session -> { - TODO JNO - UPDATE THIS WITH WRITABLE PRODUCT INTERFACE - // create new product, fill the data and store it - final Product product = session.createNew( - Product.class, 100 - ); + // create new product using the editor directly + session.createNewEntity( + ProductEditor.class, 100 + ) + // fill the data + .setCode("JP328a01a") + .setName("Creative OUTLIER FREE PRO", Locale.ENGLISH) + .setEAN("51EF1081AA000") + .setReferencedFiles(new Product.ReferencedFiles(5, 7)) + .addOrUpdateMarketingBrand( + 1100, + whichIs -> whichIs + .setMarket("EU") + .setBrandGroup(42) + ) + // store the product + .upsertVia(session); - // get existing product, update the data and store it - session.getEntity( - Product.class, 1, entityFetchAllContent() + // get existing product + final ProductEditor storedProduct = session.getEntity( + ProductEditor.class, 100, entityFetchAllContent() ).orElseThrow(); + + // update the data + storedProduct + .setName("Creative OUTLIER FREE PRO", Locale.GERMAN) + .addOrUpdateLicensingBrand( + 1740, + whichIs -> whichIs + .setMarket("Asia") + .setBrandGroup(31) +) + // and store the modified product back + .upsertVia(session); } ); \ No newline at end of file diff --git a/documentation/user/en/use/connectors/examples/instance-editor-example.java b/documentation/user/en/use/connectors/examples/instance-editor-example.java index 13f50b799..2596a0d99 100644 --- a/documentation/user/en/use/connectors/examples/instance-editor-example.java +++ b/documentation/user/en/use/connectors/examples/instance-editor-example.java @@ -1,31 +1,36 @@ -public interface ProductEditor implements InstanceEditor { +public interface ProductEditor extends Product, InstanceEditor { - void setCode(@Nonnull String code); + ProductEditor setCode(String code); + + ProductEditor setName(String name, Locale locale); + + ProductEditor setEAN(String ean); @AttributeRef("manufacturedBefore") - void setYears(int... year); + ProductEditor setYears(int... year); - void setReferencedFiles(@Nonnull Product.ReferencedFiles files); + ProductEditor setReferencedFiles(ReferencedFiles files); - void setParentEntity(@Nullable Integer parentId); + ProductEditor setParentEntity(Integer parentId); @Price - void setPrices(PriceContract... price); + ProductEditor setPrices(PriceContract... price); - @Reference - void setMarketingBrand(int brandId, @Nonnull Consumer brandEditor); + @ReferenceRef("marketingBrand") + ProductEditor addOrUpdateMarketingBrand(int brandId, @CreateWhenMissing Consumer brandEditor); - @Reference - void addOrUpdateLicensingBrand(int brandId, @Nonnull Consumer brandEditor); + @ReferenceRef("licensingBrands") + ProductEditor addOrUpdateLicensingBrand(int brandId, @CreateWhenMissing Consumer brandEditor); @ReferenceRef("licensingBrands") - void removeLicensingBrandById(int brandId); + @RemoveWhenExists + ProductEditor removeLicensingBrandById(int brandId); - interface BrandEditor extends Product.Brand { + interface BrandEditor extends Brand { - void setBrandGroup(@Nullable Integer brandGroupId); + BrandEditor setBrandGroup(Integer brandGroupId); - void setMarket(@NullableString market); + BrandEditor setMarket(String market); } diff --git a/documentation/user/en/use/connectors/examples/sealed-instance-example.java b/documentation/user/en/use/connectors/examples/sealed-instance-example.java index f9b7c4726..e0cf5bb15 100644 --- a/documentation/user/en/use/connectors/examples/sealed-instance-example.java +++ b/documentation/user/en/use/connectors/examples/sealed-instance-example.java @@ -4,28 +4,37 @@ EvolutionMode.ADDING_CURRENCIES } ) -public interface Product implements SealedInstance { +public interface Product extends SealedInstance, Serializable { + @PrimaryKey int getId(); - @Attribute + @Attribute( + name = "code", + description = "Unique code of the product.", + unique = AttributeUniquenessType.UNIQUE_WITHIN_COLLECTION + ) @Nonnull String getCode(); + @Attribute(localized = true) + String getName(); + + @Attribute + String getEAN(); + @Attribute( name = "manufacturedBefore", description = "How many years ago the product was manufactured.", deprecated = "This attribute is obsolete.", filterable = true ) - @Nonnull default int[] getYears() { // the default implementation defines default value return new int[] {1978,2005,2020}; } @AssociatedData - @Nonnull ReferencedFiles getReferencedFiles(); @ParentEntity @@ -34,10 +43,10 @@ default int[] getYears() { @PriceForSale PriceContract getSellingPrice(); - @Reference + @Reference(indexed = true, managed = false, groupEntityManaged = false) Brand getMarketingBrand(); - @Reference + @Reference(managed = false, groupEntityManaged = false) Brand[] getLicensingBrands(); record ReferencedFiles(@Nonnull int... fileId) implements Serializable {} diff --git a/documentation/user/en/use/connectors/examples/sealed-open-lifecycle-example.java b/documentation/user/en/use/connectors/examples/sealed-open-lifecycle-example.java index 10a29105c..8bde3cbe5 100644 --- a/documentation/user/en/use/connectors/examples/sealed-open-lifecycle-example.java +++ b/documentation/user/en/use/connectors/examples/sealed-open-lifecycle-example.java @@ -3,7 +3,7 @@ session -> { // get existing product in a read-only form - safe for multi-threaded use final Product readOnlyInstance = session.getEntity( - Product.class, 1, entityFetchAllContent() + Product.class, 100, entityFetchAllContent() ).orElseThrow(); // now create a new instance that is open for write - not safe for multi-threaded use diff --git a/documentation/user/en/use/connectors/examples/selective-imports.java b/documentation/user/en/use/connectors/examples/selective-imports.java new file mode 100644 index 000000000..7f7ecbf83 --- /dev/null +++ b/documentation/user/en/use/connectors/examples/selective-imports.java @@ -0,0 +1,2 @@ +import io.evitadb.documentation.mock.Product; +import io.evitadb.documentation.mock.ProductEditor; \ No newline at end of file diff --git a/documentation/user/en/use/connectors/java.md b/documentation/user/en/use/connectors/java.md index 84270d575..bcddf6010 100644 --- a/documentation/user/en/use/connectors/java.md +++ b/documentation/user/en/use/connectors/java.md @@ -239,7 +239,7 @@ described in detail in the [schema API chapter](../../use/api/schema-api.md#decl Entity in the form of custom contract can be read from the database using dedicated methods on evita_api/src/main/java/io/evitadb/api/EvitaSessionContract.java interface: - + [Fetching the entity using custom interface](/documentation/user/en/use/connectors/examples/custom-contract-reading.java) @@ -259,7 +259,7 @@ Read-only entity fetching is described in detail in the [read API chapter](../.. Entity in the form of custom contract can be written to the database using dedicated methods on evita_api/src/main/java/io/evitadb/api/EvitaSessionContract.java interface: - + [Writing the entity using custom interface](/documentation/user/en/use/connectors/examples/custom-contract-writing.java) @@ -283,7 +283,7 @@ We call this the "sealed/open" principle, and it works like this: You define an interface or class with final fields that are initialized in the constructor: - + [Sealed instance in custom interface](/documentation/user/en/use/connectors/examples/sealed-instance-example.java) @@ -305,7 +305,7 @@ practice to keep the schema definition and the data access interface in the same Then you define a separate interface to modify the data: - + [Instance editor in custom interface](/documentation/user/en/use/connectors/examples/instance-editor-example.java) @@ -319,7 +319,7 @@ and specifies that the `` is the `Product` interface. Now we can use the interfaces described above in the following way: - + [Opening the sealed interface](/documentation/user/en/use/connectors/examples/sealed-open-lifecycle-example.java) diff --git a/evita_api/src/main/java/io/evitadb/api/proxy/impl/ProxycianFactory.java b/evita_api/src/main/java/io/evitadb/api/proxy/impl/ProxycianFactory.java index 7fa6a360f..3b2c34111 100644 --- a/evita_api/src/main/java/io/evitadb/api/proxy/impl/ProxycianFactory.java +++ b/evita_api/src/main/java/io/evitadb/api/proxy/impl/ProxycianFactory.java @@ -225,9 +225,11 @@ private static T createProxy( if (stateInitializer != null) { stateInitializer.accept(proxyState); } + final ProxyRecipe recipe = recipeLocator.apply(cacheKey); return ByteBuddyProxyGenerator.instantiate( - recipeLocator.apply(cacheKey), - proxyState + recipe, + proxyState, + recipe.getInterfaces()[0].getClassLoader() ); } else if (!isFinal(expectedType)) { final BestMatchingEntityConstructorWithExtractionLambda bestMatchingConstructor = findBestMatchingConstructor( @@ -241,11 +243,13 @@ private static T createProxy( if (stateInitializer != null) { stateInitializer.accept(proxyState); } + final ProxyRecipe recipe = recipeLocator.apply(cacheKey); return ByteBuddyProxyGenerator.instantiate( - recipeLocator.apply(cacheKey), + recipe, proxyState, bestMatchingConstructor.constructor().getParameterTypes(), - bestMatchingConstructor.constructorArguments(entity) + bestMatchingConstructor.constructorArguments(entity), + recipe.getInterfaces()[0].getClassLoader() ); } else { final BestMatchingEntityConstructorWithExtractionLambda bestMatchingConstructor = findBestMatchingConstructor( @@ -293,15 +297,17 @@ private static T createReferenceProxy( ); } else { if (expectedType.isInterface()) { + final ProxyRecipe recipe = recipeLocator.apply(cacheKey); return ByteBuddyProxyGenerator.instantiate( - recipeLocator.apply(cacheKey), + recipe, new SealedEntityReferenceProxyState( entity, entityPrimaryKeySupplier, referencedEntitySchemas, reference, mainType, expectedType, recipes, collectedRecipes, reflectionLookup, instanceCache - ) + ), + recipe.getInterfaces()[0].getClassLoader() ); } else if (isAbstract(expectedType)) { final BestMatchingReferenceConstructorWithExtractionLambda bestMatchingConstructor = findBestMatchingConstructor( @@ -309,8 +315,9 @@ private static T createReferenceProxy( reference.getReferenceSchemaOrThrow(), reflectionLookup, new DirectProxyFactory(recipes, collectedRecipes, reflectionLookup) ); + final ProxyRecipe recipe = recipeLocator.apply(cacheKey); return ByteBuddyProxyGenerator.instantiate( - recipeLocator.apply(cacheKey), + recipe, new SealedEntityReferenceProxyState( entity, entityPrimaryKeySupplier, referencedEntitySchemas, reference, @@ -319,7 +326,8 @@ private static T createReferenceProxy( instanceCache ), bestMatchingConstructor.constructor().getParameterTypes(), - bestMatchingConstructor.constructorArguments(entity, reference) + bestMatchingConstructor.constructorArguments(entity, reference), + recipe.getInterfaces()[0].getClassLoader() ); } else { final BestMatchingReferenceConstructorWithExtractionLambda bestMatchingConstructor = findBestMatchingConstructor( diff --git a/evita_api/src/main/java/io/evitadb/api/proxy/impl/entity/GetAssociatedDataMethodClassifier.java b/evita_api/src/main/java/io/evitadb/api/proxy/impl/entity/GetAssociatedDataMethodClassifier.java index 9985c0776..785b571ae 100644 --- a/evita_api/src/main/java/io/evitadb/api/proxy/impl/entity/GetAssociatedDataMethodClassifier.java +++ b/evita_api/src/main/java/io/evitadb/api/proxy/impl/entity/GetAssociatedDataMethodClassifier.java @@ -71,6 +71,7 @@ import static io.evitadb.api.proxy.impl.ProxyUtils.getResolvedTypes; import static io.evitadb.dataType.data.ComplexDataObjectConverter.getOriginalForm; +import static java.util.Optional.ofNullable; /** * Identifies methods that are used to get associated data from an entity and provides their implementation. @@ -181,7 +182,11 @@ public static AssociatedDataSchemaContract getAssociatedDataSchema( final AssociatedData associatedDataInstance = reflectionLookup.getAnnotationInstanceForProperty(method, AssociatedData.class); final AssociatedDataRef associatedDataRefInstance = reflectionLookup.getAnnotationInstanceForProperty(method, AssociatedDataRef.class); if (associatedDataInstance != null) { - return entitySchema.getAssociatedDataOrThrowException(associatedDataInstance.name()); + return entitySchema.getAssociatedDataOrThrowException( + ofNullable(associatedDataInstance.name()) + .filter(it -> !it.isBlank()) + .orElseGet(() -> ReflectionLookup.getPropertyNameFromMethodName(method.getName())) + ); } else if (associatedDataRefInstance != null) { return entitySchema.getAssociatedDataOrThrowException(associatedDataRefInstance.value()); } else if (!reflectionLookup.hasAnnotationForPropertyInSamePackage(method, AssociatedData.class) && ClassUtils.isAbstract(method)) { diff --git a/evita_api/src/main/java/io/evitadb/api/proxy/impl/entity/GetAttributeMethodClassifier.java b/evita_api/src/main/java/io/evitadb/api/proxy/impl/entity/GetAttributeMethodClassifier.java index 6e07fb503..f6a690385 100644 --- a/evita_api/src/main/java/io/evitadb/api/proxy/impl/entity/GetAttributeMethodClassifier.java +++ b/evita_api/src/main/java/io/evitadb/api/proxy/impl/entity/GetAttributeMethodClassifier.java @@ -243,7 +243,11 @@ public static AttributeSchemaContract getAttributeSchema( final Function schemaLocator = attributeName -> entitySchema.getAttribute(attributeName) .orElseThrow(() -> new AttributeNotFoundException(attributeName, entitySchema)); if (attributeInstance != null) { - return schemaLocator.apply(attributeInstance.name()); + return schemaLocator.apply( + ofNullable(attributeInstance.name()) + .filter(it -> !it.isBlank()) + .orElseGet(() -> ReflectionLookup.getPropertyNameFromMethodName(method.getName())) + ); } else if (attributeRefInstance != null) { return schemaLocator.apply(attributeRefInstance.value()); } else if (!reflectionLookup.hasAnnotationForPropertyInSamePackage(method, Attribute.class) && ClassUtils.isAbstract(method)) { diff --git a/evita_api/src/main/java/io/evitadb/api/proxy/impl/entity/GetReferenceMethodClassifier.java b/evita_api/src/main/java/io/evitadb/api/proxy/impl/entity/GetReferenceMethodClassifier.java index 8bda0875a..f9ec16f2d 100644 --- a/evita_api/src/main/java/io/evitadb/api/proxy/impl/entity/GetReferenceMethodClassifier.java +++ b/evita_api/src/main/java/io/evitadb/api/proxy/impl/entity/GetReferenceMethodClassifier.java @@ -167,7 +167,11 @@ public static ReferenceSchemaContract getReferenceSchema( final Reference referenceInstance = reflectionLookup.getAnnotationInstanceForProperty(method, Reference.class); final ReferenceRef referenceRefInstance = reflectionLookup.getAnnotationInstanceForProperty(method, ReferenceRef.class); if (referenceInstance != null) { - return entitySchema.getReferenceOrThrowException(referenceInstance.name()); + return entitySchema.getReferenceOrThrowException( + ofNullable(referenceInstance.name()) + .filter(it -> !it.isBlank()) + .orElseGet(() -> ReflectionLookup.getPropertyNameFromMethodName(method.getName())) + ); } else if (referenceRefInstance != null) { return entitySchema.getReferenceOrThrowException(referenceRefInstance.value()); } else if (!reflectionLookup.hasAnnotationForPropertyInSamePackage(method, Reference.class) && ClassUtils.isAbstract(method)) { diff --git a/evita_api/src/main/java/io/evitadb/api/proxy/impl/reference/GetReferenceAttributeMethodClassifier.java b/evita_api/src/main/java/io/evitadb/api/proxy/impl/reference/GetReferenceAttributeMethodClassifier.java index c8f125b1b..cc2acb3c1 100644 --- a/evita_api/src/main/java/io/evitadb/api/proxy/impl/reference/GetReferenceAttributeMethodClassifier.java +++ b/evita_api/src/main/java/io/evitadb/api/proxy/impl/reference/GetReferenceAttributeMethodClassifier.java @@ -189,7 +189,11 @@ public static AttributeSchemaContract getAttributeSchema( () -> new AttributeNotFoundException(attributeName, referenceSchema, entitySchema) ); if (attributeInstance != null) { - return schemaLocator.apply(attributeInstance.name()); + return schemaLocator.apply( + ofNullable(attributeInstance.name()) + .filter(it -> !it.isBlank()) + .orElseGet(() -> ReflectionLookup.getPropertyNameFromMethodName(method.getName())) + ); } else if (attributeRefInstance != null) { return schemaLocator.apply(attributeRefInstance.value()); } else if (!reflectionLookup.hasAnnotationInSamePackage(method, Attribute.class) && ClassUtils.isAbstract(method)) { diff --git a/evita_api/src/main/java/io/evitadb/api/requestResponse/schema/ClassSchemaAnalyzer.java b/evita_api/src/main/java/io/evitadb/api/requestResponse/schema/ClassSchemaAnalyzer.java index 9329d7215..5ca4b2820 100644 --- a/evita_api/src/main/java/io/evitadb/api/requestResponse/schema/ClassSchemaAnalyzer.java +++ b/evita_api/src/main/java/io/evitadb/api/requestResponse/schema/ClassSchemaAnalyzer.java @@ -68,6 +68,7 @@ import java.util.stream.Collectors; import static java.util.Optional.empty; +import static java.util.Optional.of; import static java.util.Optional.ofNullable; /** @@ -141,11 +142,15 @@ public static Optional extractEntityTypeFromClass(@Nonnull Class clas clazz -> { final EntityRef entityRef = reflectionLookup.getClassAnnotation(clazz, EntityRef.class); if (entityRef != null) { - return ofNullable(entityRef.value()); + return ofNullable(entityRef.value()) + .filter(it -> !it.isBlank()) + .or(() -> of(reflectionLookup.findOriginClass(clazz, entityRef).getSimpleName())); } final Entity entity = reflectionLookup.getClassAnnotation(clazz, Entity.class); if (entity != null) { - return ofNullable(entity.name()); + return ofNullable(entity.name()) + .filter(it -> !it.isBlank()) + .or(() -> of(reflectionLookup.findOriginClass(clazz, entity).getSimpleName())); } return empty(); } diff --git a/evita_common/src/main/java/io/evitadb/utils/ReflectionLookup.java b/evita_common/src/main/java/io/evitadb/utils/ReflectionLookup.java index 3f7ca9f15..f6cae41be 100644 --- a/evita_common/src/main/java/io/evitadb/utils/ReflectionLookup.java +++ b/evita_common/src/main/java/io/evitadb/utils/ReflectionLookup.java @@ -696,6 +696,43 @@ public T getClassAnnotation(@Nonnull Class type, @Nonn return result.isEmpty() ? null : result.get(0); } + /** + * Returns the class the passed annotation belongs to. + * + * @param clazz class to start the search from + * @param annotationInstance annotation instance to find the origin class for + * @return the class the passed annotation belongs to + * @throws IllegalArgumentException if the annotation is not present on the class + */ + @Nonnull + public Class findOriginClass(@Nonnull Class clazz, @Nonnull Annotation annotationInstance) { + Class examinedClass = clazz; + do { + final Annotation[] someAnnotation = examinedClass.getAnnotations(); + if (someAnnotation.length > 0) { + for (Annotation annotation : expand(someAnnotation)) { + if (annotation == annotationInstance) { + return examinedClass; + } + } + } + for (Class implementedInterface : examinedClass.getInterfaces()) { + final List interfaceAnnotation = getClassAnnotations(implementedInterface); + if (!interfaceAnnotation.isEmpty()) { + for (Annotation annotation : expand(interfaceAnnotation.toArray(new Annotation[0]))) { + if (annotation == annotationInstance) { + return implementedInterface; + } + } + } + } + + examinedClass = examinedClass.getSuperclass(); + } while (examinedClass != null && !Objects.equals(Object.class, examinedClass)); + + throw new IllegalArgumentException("Annotation " + annotationInstance + " is not present on class " + clazz + "!"); + } + /** * Returns all annotations of certain type from the class. Annotations are also looked up in superclass/implements hierarchy. * Annotations on annotation are also taken into account. 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 6f16c01dc..2d974fe0a 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 @@ -439,7 +439,7 @@ 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/use/connectors/java.md"), ExampleFilter.values() ).stream(); } diff --git a/evita_functional_tests/src/test/java/io/evitadb/documentation/mock/Product.java b/evita_functional_tests/src/test/java/io/evitadb/documentation/mock/Product.java new file mode 100644 index 000000000..176b8e282 --- /dev/null +++ b/evita_functional_tests/src/test/java/io/evitadb/documentation/mock/Product.java @@ -0,0 +1,90 @@ +package io.evitadb.documentation.mock; + +import io.evitadb.api.requestResponse.data.PriceContract; +import io.evitadb.api.requestResponse.data.SealedInstance; +import io.evitadb.api.requestResponse.data.annotation.AssociatedData; +import io.evitadb.api.requestResponse.data.annotation.Attribute; +import io.evitadb.api.requestResponse.data.annotation.Entity; +import io.evitadb.api.requestResponse.data.annotation.ParentEntity; +import io.evitadb.api.requestResponse.data.annotation.PriceForSale; +import io.evitadb.api.requestResponse.data.annotation.PrimaryKey; +import io.evitadb.api.requestResponse.data.annotation.Reference; +import io.evitadb.api.requestResponse.data.annotation.ReferencedEntity; +import io.evitadb.api.requestResponse.data.annotation.ReferencedEntityGroup; +import io.evitadb.api.requestResponse.schema.EvolutionMode; +import io.evitadb.api.requestResponse.schema.dto.AttributeUniquenessType; + +import javax.annotation.Nonnull; +import java.io.Serializable; + +/** + * We have these classes because of `java.md` documentation file. Unfortunately, there is problem with Proxycian and + * ByteBuddy in the JShell REPL classloader and we need to declare the main interfaces on standard classpath. + */ +@Entity( + allowedEvolution = { + EvolutionMode.ADDING_LOCALES, + EvolutionMode.ADDING_CURRENCIES + } +) +public interface Product extends SealedInstance, Serializable { + + @PrimaryKey(autoGenerate = false) + int getId(); + + @Attribute( + name = "code", + description = "Unique code of the product.", + unique = AttributeUniquenessType.UNIQUE_WITHIN_COLLECTION + ) + @Nonnull + String getCode(); + + @Attribute(localized = true) + String getName(); + + @Attribute + String getEAN(); + + @Attribute( + name = "manufacturedBefore", + description = "How many years ago the product was manufactured.", + deprecated = "This attribute is obsolete.", + filterable = true + ) + default int[] getYears() { + // the default implementation defines default value + return new int[] {1978,2005,2020}; + } + + @AssociatedData + ReferencedFiles getReferencedFiles(); + + @ParentEntity + int getParentEntity(); + + @PriceForSale + PriceContract getSellingPrice(); + + @Reference(indexed = true, managed = false, groupEntityManaged = false) + Brand getMarketingBrand(); + + @Reference(managed = false, groupEntityManaged = false) + Brand[] getLicensingBrands(); + + record ReferencedFiles(@Nonnull int... fileId) implements Serializable {} + + interface Brand extends Serializable { + + @ReferencedEntity + int getBrand(); + + @ReferencedEntityGroup + int getBrandGroup(); + + @Attribute + String getMarket(); + + } + +} \ No newline at end of file diff --git a/evita_functional_tests/src/test/java/io/evitadb/documentation/mock/ProductEditor.java b/evita_functional_tests/src/test/java/io/evitadb/documentation/mock/ProductEditor.java new file mode 100644 index 000000000..7f08a297b --- /dev/null +++ b/evita_functional_tests/src/test/java/io/evitadb/documentation/mock/ProductEditor.java @@ -0,0 +1,54 @@ +package io.evitadb.documentation.mock; + +import io.evitadb.api.requestResponse.data.InstanceEditor; +import io.evitadb.api.requestResponse.data.PriceContract; +import io.evitadb.api.requestResponse.data.annotation.AttributeRef; +import io.evitadb.api.requestResponse.data.annotation.CreateWhenMissing; +import io.evitadb.api.requestResponse.data.annotation.Price; +import io.evitadb.api.requestResponse.data.annotation.ReferenceRef; +import io.evitadb.api.requestResponse.data.annotation.RemoveWhenExists; + +import java.util.Locale; +import java.util.function.Consumer; + +/** + * We have these classes because of `java.md` documentation file. Unfortunately, there is problem with Proxycian and + * ByteBuddy in the JShell REPL classloader and we need to declare the main interfaces on standard classpath. + */ +public interface ProductEditor extends Product, InstanceEditor { + + ProductEditor setCode(String code); + + ProductEditor setName(String name, Locale locale); + + ProductEditor setEAN(String ean); + + @AttributeRef("manufacturedBefore") + ProductEditor setYears(int... year); + + ProductEditor setReferencedFiles(Product.ReferencedFiles files); + + ProductEditor setParentEntity(Integer parentId); + + @Price + ProductEditor setPrices(PriceContract... price); + + @ReferenceRef("marketingBrand") + ProductEditor addOrUpdateMarketingBrand(int brandId, @CreateWhenMissing Consumer brandEditor); + + @ReferenceRef("licensingBrands") + ProductEditor addOrUpdateLicensingBrand(int brandId, @CreateWhenMissing Consumer brandEditor); + + @ReferenceRef("licensingBrands") + @RemoveWhenExists + ProductEditor removeLicensingBrandById(int brandId); + + interface BrandEditor extends Product.Brand { + + BrandEditor setBrandGroup(Integer brandGroupId); + + BrandEditor setMarket(String market); + + } + +} \ No newline at end of file diff --git a/evita_functional_tests/src/test/resources/META-INF/documentation/imports.java b/evita_functional_tests/src/test/resources/META-INF/documentation/imports.java index 416a6f782..6121ccd2b 100644 --- a/evita_functional_tests/src/test/resources/META-INF/documentation/imports.java +++ b/evita_functional_tests/src/test/resources/META-INF/documentation/imports.java @@ -67,9 +67,14 @@ import io.evitadb.api.requestResponse.EvitaEntityReferenceResponse; import io.evitadb.api.requestResponse.EvitaEntityResponse; import io.evitadb.api.requestResponse.data.EntityReferenceContract; +import io.evitadb.api.requestResponse.data.mutation.EntityMutation; import io.evitadb.api.requestResponse.data.structure.EntityReference; import io.evitadb.api.requestResponse.data.structure.InitialEntityBuilder; import io.evitadb.api.requestResponse.data.structure.ExistingEntityBuilder; +import io.evitadb.api.query.visitor.PrettyPrintingVisitor.StringWithParameters; +import io.evitadb.api.exception.ContextMissingException; +import io.evitadb.api.requestResponse.data.SealedInstance; +import io.evitadb.api.requestResponse.data.InstanceEditor; import io.evitadb.externalApi.graphql.GraphQLProvider; import io.evitadb.externalApi.grpc.GrpcProvider; import io.evitadb.externalApi.rest.RestProvider; @@ -94,6 +99,8 @@ import io.evitadb.dataType.ByteNumberRange; import io.evitadb.dataType.LongNumberRange; import io.evitadb.dataType.BigDecimalNumberRange; +import io.evitadb.api.requestResponse.schema.dto.AttributeUniquenessType; +import io.evitadb.api.requestResponse.schema.dto.GlobalAttributeUniquenessType; import io.evitadb.test.generator.DataGenerator.Labels; import io.evitadb.test.generator.DataGenerator.ReferencedFileSet; import io.evitadb.api.query.require.FacetStatisticsDepth; @@ -135,8 +142,6 @@ import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import java.util.concurrent.atomic.AtomicReference; -import io.evitadb.api.query.visitor.PrettyPrintingVisitor.StringWithParameters; -import io.evitadb.api.exception.ContextMissingException; import static io.evitadb.api.query.filter.AttributeSpecialValue.*; import static io.evitadb.api.query.require.StatisticsType.*; From 2568bedff696cb52b09c6a9cd38c4036a2660016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Novotn=C3=BD?= Date: Tue, 12 Dec 2023 15:56:45 +0100 Subject: [PATCH 33/52] docs: corrections in documentation test running --- documentation/user/en/use/api/write-data.md | 2 +- .../documentation/UserDocumentationTest.java | 36 +++++++++++-------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/documentation/user/en/use/api/write-data.md b/documentation/user/en/use/api/write-data.md index fe2e463d9..e9657d684 100644 --- a/documentation/user/en/use/api/write-data.md +++ b/documentation/user/en/use/api/write-data.md @@ -740,7 +740,7 @@ entity with the changes applied by defining requirements.
- + [Creating new entity example](/documentation/user/en/use/api/example/create-new-entity.graphql) 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 2d974fe0a..66e163d83 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 @@ -319,7 +319,7 @@ private static Executable convertToRunnable( * Returns array of files with same name and different extension from the same directory. */ @Nonnull - private static List findRelatedFiles(@Nonnull Path theFile, @Nonnull Set alreadyUsedRelatedResources, Map codeSnippetIndex) { + private static List findRelatedFiles(@Nonnull Path theFile, @Nonnull Set alreadyUsedPaths, Map codeSnippetIndex) { try (final Stream siblings = Files.list(theFile.getParent())) { final String theFileName = theFile.getFileName().toString(); final String theFileExtension = getFileNameExtension(theFile); @@ -328,13 +328,13 @@ private static List findRelatedFiles(@Nonnull Path theFile, @Nonnull Set

!codeSnippetIndex.containsKey(it)) - .peek(alreadyUsedRelatedResources::add) + .filter(it -> !alreadyUsedPaths.contains(it)) + .peek(alreadyUsedPaths::add) .toList(); } catch (IOException e) { Assertions.fail( @@ -348,7 +348,7 @@ private static List findRelatedFiles(@Nonnull Path theFile, @Nonnull Set

findVariants(@Nonnull Path theFile, @Nonnull Set alreadyUsedRelatedResources, @Nonnull String[] variants) { + private static List findVariants(@Nonnull Path theFile, @Nonnull Set alreadyUsedPaths, @Nonnull String[] variants) { Integer indexOfMainVariant = null; final String theFileName = theFile.getFileName().toString(); final String theFileExtension = getFileNameExtension(theFile); @@ -370,8 +370,8 @@ private static List findVariants(@Nonnull Path theFile, @Nonnull Set if (i != indexOfMainVariant) { final String variant = variants[i]; final Path variantFile = theFile.getParent().resolve(theFileName.replace(variants[indexOfMainVariant], variant)).normalize(); - if (!alreadyUsedRelatedResources.contains(variantFile)) { - alreadyUsedRelatedResources.add(variantFile); + if (!alreadyUsedPaths.contains(variantFile)) { + alreadyUsedPaths.add(variantFile); variantsFiles.add(variantFile); } } @@ -406,9 +406,9 @@ Stream testDocumentation() throws IOException { Environment.DEMO_SERVER, it, new ExampleFilter[]{ - // ExampleFilter.CSHARP, + ExampleFilter.CSHARP, ExampleFilter.JAVA, - // ExampleFilter.REST, + ExampleFilter.REST, ExampleFilter.GRAPHQL, ExampleFilter.EVITAQL } @@ -439,7 +439,7 @@ Stream testDocumentation() throws IOException { Stream testSingleFileDocumentation() { return this.createTests( Environment.DEMO_SERVER, - getRootDirectory().resolve("documentation/user/en/use/connectors/java.md"), + getRootDirectory().resolve("documentation/user/en/query/requirements/fetching.md"), ExampleFilter.values() ).stream(); } @@ -455,7 +455,7 @@ Stream testSingleFileDocumentation() { @Disabled Stream testSingleFileDocumentationAndCreateOtherLanguageSnippets() { return this.createTests( - Environment.LOCALHOST, + Environment.DEMO_SERVER, getRootDirectory().resolve("documentation/user/en/use/api/query-data.md"), ExampleFilter.values(), CreateSnippets.MARKDOWN, CreateSnippets.JAVA, CreateSnippets.GRAPHQL, CreateSnippets.REST, CreateSnippets.CSHARP @@ -484,7 +484,7 @@ private List createTests( final AtomicInteger index = new AtomicInteger(); final List codeSnippets = new LinkedList<>(); final TestContextProvider contextAccessor = new TestContextProvider(); - final Set alreadyUsedRelatedResources = new HashSet<>(); + final Set alreadyUsedPaths = new HashSet<>(); final Matcher sourceCodeMatcher = SOURCE_CODE_PATTERN.matcher(fileContent); while (sourceCodeMatcher.find()) { @@ -544,7 +544,14 @@ private List createTests( final boolean isLocal = sourceCodeTabsMatcher.group(4) != null && "local".equals(sourceCodeTabsMatcher.group(4).trim()); final Environment environment = isLocal ? Environment.LOCALHOST : profile; + if (profile == Environment.LOCALHOST && isLocal) { + // we need to skip the local tests when profile is localhost and the example is local + // it starts local instance of evitaDB which will fail on already opened ports + continue; + } + if (!NOT_TESTED_LANGUAGES.contains(referencedFileExtension)) { + alreadyUsedPaths.add(referencedFile); final Path[] requiredScripts = ofNullable(sourceCodeTabsMatcher.group(2)) .map( requires -> Arrays.stream(requires.split(",")) @@ -559,7 +566,7 @@ private List createTests( "Example `" + referencedFile.getFileName() + "`", referencedFileExtension, referencedFile.normalize(), - findRelatedFiles(referencedFile, alreadyUsedRelatedResources, codeSnippetIndex) + findRelatedFiles(referencedFile, alreadyUsedPaths, codeSnippetIndex) .stream() .map(relatedFile -> { final String relatedFileExtension = getFileNameExtension(relatedFile); @@ -619,13 +626,14 @@ private List createTests( .orElse(null); final String[] variants = sourceAlternativeTabsMatcher.group(3).split("\\|"); if (!NOT_TESTED_LANGUAGES.contains(referencedFileExtension)) { + alreadyUsedPaths.add(referencedFile); final List outputSnippet = ofNullable(outputSnippetIndex.get(referencedFile)) .orElse(Collections.emptyList()); final CodeSnippet codeSnippet = new CodeSnippet( "Example `" + referencedFile.getFileName() + "`", referencedFileExtension, referencedFile.normalize(), - findVariants(referencedFile, alreadyUsedRelatedResources, variants) + findVariants(referencedFile, alreadyUsedPaths, variants) .stream() .map(relatedFile -> { final String relatedFileExtension = getFileNameExtension(relatedFile); From 05420062c6313783296934aec828d265f3a488a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Wed, 13 Dec 2023 09:20:02 +0100 Subject: [PATCH 34/52] fix: gRPC access log is logging even when disabled --- .../evitadb/externalApi/grpc/utils/GrpcServer.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/evita_external_api/evita_external_api_grpc/server/src/main/java/io/evitadb/externalApi/grpc/utils/GrpcServer.java b/evita_external_api/evita_external_api_grpc/server/src/main/java/io/evitadb/externalApi/grpc/utils/GrpcServer.java index 1ba112371..2f61af2ca 100644 --- a/evita_external_api/evita_external_api_grpc/server/src/main/java/io/evitadb/externalApi/grpc/utils/GrpcServer.java +++ b/evita_external_api/evita_external_api_grpc/server/src/main/java/io/evitadb/externalApi/grpc/utils/GrpcServer.java @@ -120,13 +120,16 @@ private void setUpServer(@Nonnull Evita evita, @Nonnull ApiOptions apiOptions, @ e ); } - server = NettyServerBuilder.forAddress(new InetSocketAddress(hosts[0].host(), hosts[0].port()), tlsServerCredentials) - .intercept(new ServerSessionInterceptor(evita)) - .intercept(new GlobalExceptionHandlerInterceptor()) - .intercept(new AccessLogInterceptor()) + + final NettyServerBuilder serverBuilder = NettyServerBuilder.forAddress(new InetSocketAddress(hosts[0].host(), hosts[0].port()), tlsServerCredentials) .executor(evita.getExecutor()) .addService(new EvitaService(evita)) .addService(new EvitaSessionService(evita)) - .build(); + .intercept(new ServerSessionInterceptor(evita)) + .intercept(new GlobalExceptionHandlerInterceptor()); + if (apiOptions.accessLog()) { + serverBuilder.intercept(new AccessLogInterceptor()); + } + server = serverBuilder.build(); } } From 5e553ba39c95034f56b5b14d0dcbbe17eee7e24d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Wed, 13 Dec 2023 09:50:00 +0100 Subject: [PATCH 35/52] fix(rest-api): REST API doesn't use transactions in read-write sessions This happened when we moved from using updateCatalog/queryCatalog methods to manual session handling, but we forgot to handle transactions as well. --- .../externalApi/http/EndpointHandler.java | 22 ++++++---- .../externalApi/http/EndpointResponse.java | 3 +- .../http/NotFoundEndpointResponse.java | 2 +- .../http/SuccessEndpointResponse.java | 10 ++--- .../EvitaSessionManagingInstrumentation.java | 2 +- .../graphql/io/GraphQLHandler.java | 8 ++-- .../graphql/io/GraphQLSchemaHandler.java | 15 ++++--- .../api/openApi/OpenApiLabApiEndpoint.java | 8 ++-- .../endpoint/CatalogSchemaHandler.java | 19 ++++++--- .../endpoint/GetCatalogSchemaHandler.java | 4 +- .../endpoint/ListCatalogsHandler.java | 17 +++++--- .../resolver/endpoint/LivenessHandler.java | 6 +-- .../endpoint/QueryEntitiesHandler.java | 25 +++++++---- .../evitadb/externalApi/rest/RestManager.java | 2 +- .../io/evitadb/externalApi/rest/api/Rest.java | 2 +- .../resolver/endpoint/CollectionsHandler.java | 6 +-- .../DeleteEntitiesByQueryHandler.java | 41 ++++++++++++++++--- .../endpoint/DeleteEntityHandler.java | 8 ++-- .../resolver/endpoint/EntityHandler.java | 12 ++++-- .../resolver/endpoint/GetEntityHandler.java | 8 ++-- .../endpoint/GetUnknownEntityHandler.java | 8 ++-- .../endpoint/ListEntitiesHandler.java | 17 +++++--- .../endpoint/ListUnknownEntitiesHandler.java | 17 +++++--- .../endpoint/QueryEntitiesHandler.java | 22 ++++++---- .../QueryOrientedEntitiesHandler.java | 2 +- .../endpoint/UpsertEntityHandler.java | 6 +-- .../endpoint/CatalogSchemaHandler.java | 12 ++++-- .../endpoint/EntitySchemaHandler.java | 12 ++++-- .../endpoint/GetCatalogSchemaHandler.java | 7 +++- .../endpoint/GetEntitySchemaHandler.java | 6 +-- .../endpoint/UpdateCatalogSchemaHandler.java | 4 +- .../endpoint/UpdateEntitySchemaHandler.java | 4 +- .../api/openApi/OpenApiCatalogEndpoint.java | 8 ++-- .../openApi/OpenApiCollectionEndpoint.java | 8 ++-- .../rest/api/openApi/OpenApiEndpoint.java | 4 +- .../api/openApi/OpenApiSystemEndpoint.java | 8 ++-- .../endpoint/OpenApiSpecificationHandler.java | 9 ++-- .../resolver/endpoint/CatalogHandler.java | 12 ++++-- .../endpoint/CreateCatalogHandler.java | 4 +- .../endpoint/DeleteCatalogHandler.java | 8 ++-- .../resolver/endpoint/GetCatalogHandler.java | 6 +-- .../endpoint/ListCatalogsHandler.java | 18 +++++--- .../resolver/endpoint/LivenessHandler.java | 6 +-- .../endpoint/UpdateCatalogHandler.java | 8 ++-- .../externalApi/rest/io/CorsEndpoint.java | 2 +- .../externalApi/rest/io/JsonRestHandler.java | 9 ++-- .../rest/io/RestEndpointExchange.java | 21 ++++++++-- .../rest/io/RestEndpointHandler.java | 19 +++++++-- 48 files changed, 317 insertions(+), 170 deletions(-) diff --git a/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/http/EndpointHandler.java b/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/http/EndpointHandler.java index f5af24653..ec7152711 100644 --- a/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/http/EndpointHandler.java +++ b/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/http/EndpointHandler.java @@ -56,7 +56,7 @@ * * @author Lukáš Hornych, FG Forrest a.s. (c) 2023 */ -public abstract class EndpointHandler implements HttpHandler { +public abstract class EndpointHandler implements HttpHandler { private static final String CONTENT_TYPE_CHARSET = "; charset=UTF-8"; @@ -71,12 +71,13 @@ public void handleRequest(HttpServerExchange serverExchange) { getPreferredResponseContentType(serverExchange).orElse(null) )) { beforeRequestHandled(exchange); + final EndpointResponse response = doHandleRequest(exchange); + afterRequestHandled(exchange, response); - final EndpointResponse response = doHandleRequest(exchange); if (response instanceof NotFoundEndpointResponse) { throw new HttpExchangeException(StatusCodes.NOT_FOUND, "Requested resource wasn't found."); - } else if (response instanceof SuccessEndpointResponse successResponse) { - final R result = successResponse.getResult(); + } else if (response instanceof SuccessEndpointResponse successResponse) { + final Object result = successResponse.getResult(); if (result == null) { sendEmptySuccessResponse(exchange); } else { @@ -104,11 +105,18 @@ protected void beforeRequestHandled(@Nonnull E exchange) { // default implementation does nothing } + /** + * Hook method called after actual endpoint handling logic is executed. Default implementation does nothing. + */ + protected void afterRequestHandled(@Nonnull E exchange, @Nonnull EndpointResponse response) { + // default implementation does nothing + } + /** * Actual endpoint logic. */ @Nonnull - protected abstract EndpointResponse doHandleRequest(@Nonnull E exchange); + protected abstract EndpointResponse doHandleRequest(@Nonnull E exchange); @Nonnull protected abstract T createInternalError(@Nonnull String message); @@ -289,7 +297,7 @@ protected T parseRequestBody(@Nonnull E exchange, @Nonnull Class dataClas * @param outputStream output stream to write serialized data to * @param result result data from handler to write to response */ - protected void writeResult(@Nonnull E exchange, @Nonnull OutputStream outputStream, @Nonnull R result) { + protected void writeResult(@Nonnull E exchange, @Nonnull OutputStream outputStream, @Nonnull Object result) { throw createInternalError("Cannot serialize response body because handler doesn't support it."); } @@ -298,7 +306,7 @@ private void sendEmptySuccessResponse(@Nonnull E exchange) { exchange.serverExchange().endExchange(); } - private void sendSuccessResponseWithBody(@Nonnull E exchange, @Nonnull R result) { + private void sendSuccessResponseWithBody(@Nonnull E exchange, @Nonnull Object result) { exchange.serverExchange().setStatusCode(StatusCodes.OK); exchange.serverExchange().getResponseHeaders().put(Headers.CONTENT_TYPE, exchange.preferredResponseContentType() + CONTENT_TYPE_CHARSET); writeResult(exchange, exchange.serverExchange().getOutputStream(), result); diff --git a/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/http/EndpointResponse.java b/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/http/EndpointResponse.java index 5283c2031..7e73f2303 100644 --- a/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/http/EndpointResponse.java +++ b/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/http/EndpointResponse.java @@ -26,8 +26,7 @@ /** * Response of {@link EndpointHandler}. It is used to determine HTTP response and its body. * - * @param type of response body * @author Lukáš Hornych, 2023 */ -public interface EndpointResponse { +public interface EndpointResponse { } diff --git a/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/http/NotFoundEndpointResponse.java b/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/http/NotFoundEndpointResponse.java index 9af51163e..7af06ac41 100644 --- a/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/http/NotFoundEndpointResponse.java +++ b/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/http/NotFoundEndpointResponse.java @@ -28,5 +28,5 @@ * * @author Lukáš Hornych, FG Forrest a.s. (c) 2023 */ -public record NotFoundEndpointResponse() implements EndpointResponse { +public record NotFoundEndpointResponse() implements EndpointResponse { } diff --git a/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/http/SuccessEndpointResponse.java b/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/http/SuccessEndpointResponse.java index 7df3f77b9..d973a6280 100644 --- a/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/http/SuccessEndpointResponse.java +++ b/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/http/SuccessEndpointResponse.java @@ -30,22 +30,22 @@ import javax.annotation.Nullable; /** - * Represents successful response. Either {@link StatusCodes#OK} or {@link StatusCodes#NO_CONTENT} depending on passed body - * object. + * Represents a successful response. Either {@link StatusCodes#OK} or {@link StatusCodes#NO_CONTENT} depending on passed body + * object. The result object must be in serializable form and thus be ready to be serialized. * * @author Lukáš Hornych, FG Forrest a.s. (c) 2023 */ -public class SuccessEndpointResponse implements EndpointResponse { +public class SuccessEndpointResponse implements EndpointResponse { @Nullable @Getter - private final R result; + private final Object result; public SuccessEndpointResponse() { this.result = null; } - public SuccessEndpointResponse(@Nonnull R result) { + public SuccessEndpointResponse(@Nonnull Object result) { this.result = result; } } diff --git a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/EvitaSessionManagingInstrumentation.java b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/EvitaSessionManagingInstrumentation.java index fcadc309a..4dfabdabe 100644 --- a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/EvitaSessionManagingInstrumentation.java +++ b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/EvitaSessionManagingInstrumentation.java @@ -74,7 +74,7 @@ public ExecutionContext instrumentExecutionContext(@Nonnull ExecutionContext exe evitaSession = evita.createReadWriteSession(catalogName); final CatalogContract catalog = evita.getCatalogInstance(catalogName) .orElseThrow(() -> new GraphQLInternalError("Catalog `" + catalogName + "` could not be found.")); - if (catalog.getCatalogState().equals(CatalogState.ALIVE)) { + if (catalog.supportsTransaction()) { evitaSession.openTransaction(); } } else { diff --git a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/io/GraphQLHandler.java b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/io/GraphQLHandler.java index 4a5083672..aabcfa178 100644 --- a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/io/GraphQLHandler.java +++ b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/io/GraphQLHandler.java @@ -72,7 +72,7 @@ * @author Lukáš Hornych, FG Forrest a.s. 2022 */ @Slf4j -public class GraphQLHandler extends EndpointHandler> { +public class GraphQLHandler extends EndpointHandler { /** * Set of GraphQL exceptions that are caused by invalid user input and thus shouldn't return server error. @@ -114,7 +114,7 @@ protected GraphQLEndpointExchange createEndpointExchange(@Nonnull HttpServerExch @Override @Nonnull - protected EndpointResponse> doHandleRequest(@Nonnull GraphQLEndpointExchange exchange) { + protected EndpointResponse doHandleRequest(@Nonnull GraphQLEndpointExchange exchange) { final GraphQLRequest graphQLRequest = parseRequestBody(exchange, GraphQLRequest.class); final ClientContextExtension clientContextExtension = graphQLRequest.clientContextExtension(); final GraphQLResponse graphQLResponse = clientContext.executeWithClientAndRequestId( @@ -123,7 +123,7 @@ protected EndpointResponse> doHandleRequest(@Nonnull GraphQLE clientContextExtension.requestId(), () -> executeRequest(graphQLRequest) ); - return new SuccessEndpointResponse<>(graphQLResponse); + return new SuccessEndpointResponse(graphQLResponse); } @Nonnull @@ -222,7 +222,7 @@ private GraphQLResponse executeRequest(@Nonnull GraphQLRequest graphQLRequest } @Override - protected void writeResult(@Nonnull GraphQLEndpointExchange exchange, @Nonnull OutputStream outputStream, @Nonnull GraphQLResponse response) { + protected void writeResult(@Nonnull GraphQLEndpointExchange exchange, @Nonnull OutputStream outputStream, @Nonnull Object response) { try { objectMapper.writeValue(outputStream, response); } catch (IOException e) { diff --git a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/io/GraphQLSchemaHandler.java b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/io/GraphQLSchemaHandler.java index ecbf48f8e..b13d6b134 100644 --- a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/io/GraphQLSchemaHandler.java +++ b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/io/GraphQLSchemaHandler.java @@ -36,6 +36,7 @@ import io.evitadb.externalApi.http.EndpointHandler; import io.evitadb.externalApi.http.EndpointResponse; import io.evitadb.externalApi.http.SuccessEndpointResponse; +import io.evitadb.utils.Assert; import io.undertow.server.HttpServerExchange; import io.undertow.util.Methods; import lombok.extern.slf4j.Slf4j; @@ -57,7 +58,7 @@ * @author Lukáš Hornych, FG Forrest a.s. 2023 */ @Slf4j -public class GraphQLSchemaHandler extends EndpointHandler { +public class GraphQLSchemaHandler extends EndpointHandler { private static final Set IMPLICIT_DIRECTIVES = Set.of("deprecated", "skip", "include", "specifiedBy"); @@ -83,8 +84,8 @@ protected GraphQLEndpointExchange createEndpointExchange(@Nonnull HttpServerExch @Override @Nonnull - protected EndpointResponse doHandleRequest(@Nonnull GraphQLEndpointExchange exchange) { - return new SuccessEndpointResponse<>(graphQL.get().getGraphQLSchema()); + protected EndpointResponse doHandleRequest(@Nonnull GraphQLEndpointExchange exchange) { + return new SuccessEndpointResponse(graphQL.get().getGraphQLSchema()); } @Nonnull @@ -124,8 +125,12 @@ public LinkedHashSet getSupportedResponseContentTypes() { @Override - protected void writeResult(@Nonnull GraphQLEndpointExchange exchange, @Nonnull OutputStream outputStream, @Nonnull GraphQLSchema response) { - final String printedSchema = schemaPrinter.print(response); + protected void writeResult(@Nonnull GraphQLEndpointExchange exchange, @Nonnull OutputStream outputStream, @Nonnull Object response) { + Assert.isPremiseValid( + response instanceof GraphQLSchema, + () -> new GraphQLInternalError("Expected response to be instance of GraphQLSchema, but was `" + response.getClass().getName() + "`.") + ); + final String printedSchema = schemaPrinter.print((GraphQLSchema) response); try (PrintWriter writer = new PrintWriter(outputStream)) { writer.write(printedSchema); } diff --git a/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/openApi/OpenApiLabApiEndpoint.java b/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/openApi/OpenApiLabApiEndpoint.java index 105536333..f086338cf 100644 --- a/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/openApi/OpenApiLabApiEndpoint.java +++ b/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/openApi/OpenApiLabApiEndpoint.java @@ -70,7 +70,7 @@ private OpenApiLabApiEndpoint(@Nonnull PathItem.HttpMethod method, @Nonnull List parameters, @Nullable OpenApiSimpleType requestBody, @Nonnull OpenApiSimpleType successResponse, - @Nonnull Function> handlerBuilder) { + @Nonnull Function> handlerBuilder) { super(method, path, false, operationId, description, deprecationNotice, parameters, requestBody, successResponse, handlerBuilder); } @@ -84,7 +84,7 @@ public static Builder newLabApiEndpoint() { @Nonnull @Override - public RestEndpointHandler toHandler(@Nonnull ObjectMapper objectMapper, + public RestEndpointHandler toHandler(@Nonnull ObjectMapper objectMapper, @Nonnull Evita evita, @Nonnull OpenAPI openApi, @Nonnull Map>> enumMapping) { @@ -111,7 +111,7 @@ public static class Builder { @Nullable private OpenApiSimpleType requestBody; @Nullable private OpenApiSimpleType successResponse; - @Nullable private Function> handlerBuilder; + @Nullable private Function> handlerBuilder; private Builder() { this.parameters = new LinkedList<>(); @@ -218,7 +218,7 @@ public Builder successResponse(@Nonnull OpenApiSimpleType successResponseType) { * Sets handler builder. */ @Nonnull - public Builder handler(@Nonnull Function> handlerBuilder) { + public Builder handler(@Nonnull Function> handlerBuilder) { this.handlerBuilder = handlerBuilder; return this; } diff --git a/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/resolver/endpoint/CatalogSchemaHandler.java b/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/resolver/endpoint/CatalogSchemaHandler.java index dd048bafe..82b279daa 100644 --- a/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/resolver/endpoint/CatalogSchemaHandler.java +++ b/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/resolver/endpoint/CatalogSchemaHandler.java @@ -26,12 +26,13 @@ import io.evitadb.api.CatalogContract; import io.evitadb.api.EvitaSessionContract; import io.evitadb.api.requestResponse.schema.CatalogSchemaContract; -import io.evitadb.externalApi.api.ExternalApiNamingConventions; import io.evitadb.externalApi.lab.api.model.CatalogsHeaderDescriptor; import io.evitadb.externalApi.rest.api.catalog.schemaApi.resolver.serializer.CatalogSchemaJsonSerializer; +import io.evitadb.externalApi.rest.exception.RestInternalError; import io.evitadb.externalApi.rest.exception.RestInvalidArgumentException; import io.evitadb.externalApi.rest.io.JsonRestHandler; import io.evitadb.externalApi.rest.io.RestEndpointExchange; +import io.evitadb.utils.Assert; import lombok.extern.slf4j.Slf4j; import javax.annotation.Nonnull; @@ -46,7 +47,7 @@ * @author Lukáš Hornych, FG Forrest a.s. (c) 2023 */ @Slf4j -public abstract class CatalogSchemaHandler extends JsonRestHandler { +public abstract class CatalogSchemaHandler extends JsonRestHandler { @Nonnull private final CatalogSchemaJsonSerializer catalogSchemaJsonSerializer; @@ -73,7 +74,11 @@ protected Optional createSession(@Nonnull RestEndpointExch .orElseThrow(() -> new RestInvalidArgumentException("Catalog `" + catalogName + "` does not exist.")); if (modifiesData()) { - return Optional.of(restApiHandlingContext.getEvita().createReadWriteSession(catalog.getName())); + final EvitaSessionContract session = restApiHandlingContext.getEvita().createReadWriteSession(catalog.getName()); + if (catalog.supportsTransaction()) { + session.openTransaction(); + } + return Optional.of(session); } else { return Optional.of(restApiHandlingContext.getEvita().createReadOnlySession(catalog.getName())); } @@ -81,9 +86,13 @@ protected Optional createSession(@Nonnull RestEndpointExch @Nonnull @Override - protected Object convertResultIntoSerializableObject(@Nonnull RestEndpointExchange exchange, @Nonnull CatalogSchemaContract catalogSchema) { + protected Object convertResultIntoSerializableObject(@Nonnull RestEndpointExchange exchange, @Nonnull Object catalogSchema) { + Assert.isPremiseValid( + catalogSchema instanceof CatalogSchemaContract, + () -> new RestInternalError("Expected catalog schema, but got `" + catalogSchema.getClass().getName() + "`.") + ); return catalogSchemaJsonSerializer.serialize( - catalogSchema, + (CatalogSchemaContract) catalogSchema, exchange.session()::getEntitySchemaOrThrow, exchange.session().getAllEntityTypes() ); diff --git a/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/resolver/endpoint/GetCatalogSchemaHandler.java b/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/resolver/endpoint/GetCatalogSchemaHandler.java index 5157bdbf9..ae11bffea 100644 --- a/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/resolver/endpoint/GetCatalogSchemaHandler.java +++ b/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/resolver/endpoint/GetCatalogSchemaHandler.java @@ -47,8 +47,8 @@ public GetCatalogSchemaHandler(@Nonnull LabApiHandlingContext labApiHandlingCont @Override @Nonnull - protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { - return new SuccessEndpointResponse<>(exchange.session().getCatalogSchema()); + protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { + return new SuccessEndpointResponse(convertResultIntoSerializableObject(exchange, exchange.session().getCatalogSchema())); } @Nonnull diff --git a/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/resolver/endpoint/ListCatalogsHandler.java b/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/resolver/endpoint/ListCatalogsHandler.java index c2c8673a0..2572058fd 100644 --- a/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/resolver/endpoint/ListCatalogsHandler.java +++ b/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/resolver/endpoint/ListCatalogsHandler.java @@ -28,8 +28,10 @@ import io.evitadb.externalApi.http.EndpointResponse; import io.evitadb.externalApi.http.SuccessEndpointResponse; import io.evitadb.externalApi.rest.api.system.resolver.serializer.CatalogJsonSerializer; +import io.evitadb.externalApi.rest.exception.RestInternalError; import io.evitadb.externalApi.rest.io.JsonRestHandler; import io.evitadb.externalApi.rest.io.RestEndpointExchange; +import io.evitadb.utils.Assert; import io.undertow.util.Methods; import javax.annotation.Nonnull; @@ -42,7 +44,7 @@ * * @author Lukáš Hornych, FG Forrest a.s. (c) 2023 */ -public class ListCatalogsHandler extends JsonRestHandler, LabApiHandlingContext> { +public class ListCatalogsHandler extends JsonRestHandler { @Nonnull private final CatalogJsonSerializer catalogJsonSerializer; @@ -54,9 +56,9 @@ public ListCatalogsHandler(@Nonnull LabApiHandlingContext restApiHandlingContext @Nonnull @Override - protected EndpointResponse> doHandleRequest(@Nonnull RestEndpointExchange exchange) { + protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { final Collection catalogs = restApiHandlingContext.getEvita().getCatalogs(); - return new SuccessEndpointResponse<>(catalogs); + return new SuccessEndpointResponse(convertResultIntoSerializableObject(exchange, catalogs)); } @Nonnull @@ -73,7 +75,12 @@ public LinkedHashSet getSupportedResponseContentTypes() { @Nonnull @Override - protected JsonNode convertResultIntoSerializableObject(@Nonnull RestEndpointExchange exchange, @Nonnull Collection catalogs) { - return catalogJsonSerializer.serialize(catalogs); + protected JsonNode convertResultIntoSerializableObject(@Nonnull RestEndpointExchange exchange, @Nonnull Object catalogs) { + Assert.isPremiseValid( + catalogs instanceof Collection, + () -> new RestInternalError("Expected collection of catalogs, but got `" + catalogs.getClass().getName() + "`.") + ); + //noinspection unchecked + return catalogJsonSerializer.serialize((Collection) catalogs); } } diff --git a/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/resolver/endpoint/LivenessHandler.java b/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/resolver/endpoint/LivenessHandler.java index 9a468118f..1fd5a2ebd 100644 --- a/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/resolver/endpoint/LivenessHandler.java +++ b/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/resolver/endpoint/LivenessHandler.java @@ -39,7 +39,7 @@ * * @author Lukáš Hornych, FG Forrest a.s. (c) 2023 */ -public class LivenessHandler extends JsonRestHandler { +public class LivenessHandler extends JsonRestHandler { public LivenessHandler(@Nonnull LabApiHandlingContext labApiHandlingContext) { super(labApiHandlingContext); @@ -47,8 +47,8 @@ public LivenessHandler(@Nonnull LabApiHandlingContext labApiHandlingContext) { @Nonnull @Override - protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { - return new SuccessEndpointResponse<>(new LivenessDto(true)); + protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { + return new SuccessEndpointResponse(convertResultIntoSerializableObject(exchange, new LivenessDto(true))); } @Nonnull diff --git a/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/resolver/endpoint/QueryEntitiesHandler.java b/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/resolver/endpoint/QueryEntitiesHandler.java index ac67ae813..eac581a52 100644 --- a/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/resolver/endpoint/QueryEntitiesHandler.java +++ b/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/resolver/endpoint/QueryEntitiesHandler.java @@ -47,6 +47,7 @@ import io.evitadb.externalApi.rest.exception.RestInternalError; import io.evitadb.externalApi.rest.io.JsonRestHandler; import io.evitadb.externalApi.rest.io.RestEndpointExchange; +import io.evitadb.utils.Assert; import io.evitadb.utils.StringUtils; import io.undertow.util.Methods; import lombok.extern.slf4j.Slf4j; @@ -64,7 +65,7 @@ * @author Lukáš Hornych, FG Forrest a.s. (c) 2023 */ @Slf4j -public class QueryEntitiesHandler extends JsonRestHandler, LabApiHandlingContext> { +public class QueryEntitiesHandler extends JsonRestHandler { @Nonnull private final QueryParser queryParser; @Nonnull private final GenericEntityJsonSerializer entityJsonSerializer; @@ -94,12 +95,12 @@ protected Optional createSession(@Nonnull RestEndpointExch @Nonnull @Override - protected EndpointResponse> doHandleRequest(@Nonnull RestEndpointExchange exchange) { + protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { final Query query = resolveQuery(exchange); log.debug("Generated evitaDB query for entity query is `{}`.", query); final EvitaResponse response = exchange.session().query(query, EntityClassifier.class); - return new SuccessEndpointResponse<>(response); + return new SuccessEndpointResponse(convertResultIntoSerializableObject(exchange, response)); } @Nonnull @@ -130,17 +131,23 @@ protected Query resolveQuery(@Nonnull RestEndpointExchange exchange) { @Nonnull @Override - protected Object convertResultIntoSerializableObject(@Nonnull RestEndpointExchange exchange, @Nonnull EvitaResponse result) { + protected Object convertResultIntoSerializableObject(@Nonnull RestEndpointExchange exchange, @Nonnull Object result) { + Assert.isPremiseValid( + result instanceof EvitaResponse, + () -> new RestInternalError("Expected evitaDB response, but got `" + result.getClass().getName() + "`.") + ); + //noinspection unchecked + final EvitaResponse evitaResponse = (EvitaResponse) result; final QueryResponseBuilder queryResponseBuilder = QueryResponse.builder() - .recordPage(serializeRecordPage(result)); - if (!result.getExtraResults().isEmpty()) { + .recordPage(serializeRecordPage(evitaResponse)); + if (!evitaResponse.getExtraResults().isEmpty()) { queryResponseBuilder .extraResults( extraResultsJsonSerializer.serialize( - result.getExtraResults(), + evitaResponse.getExtraResults(), exchange.session() - .getEntitySchema(result.getSourceQuery().getCollection().getEntityType()) - .orElseThrow(() -> new RestInternalError("No entity schema found for entity type `" + result.getSourceQuery().getCollection().getEntityType() + "`.")) + .getEntitySchema(evitaResponse.getSourceQuery().getCollection().getEntityType()) + .orElseThrow(() -> new RestInternalError("No entity schema found for entity type `" + evitaResponse.getSourceQuery().getCollection().getEntityType() + "`.")) ) ); } diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/RestManager.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/RestManager.java index 58661d1ed..c1d14fc4f 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/RestManager.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/RestManager.java @@ -201,7 +201,7 @@ private void registerSystemRestEndpoint(@Nonnull Rest.Endpoint endpoint) { /** * Registers endpoints into router. Also CORS endpoint is created automatically for this endpoint. */ - private void registerRestEndpoint(@Nonnull HttpString method, @Nonnull UriPath path, @Nonnull RestEndpointHandler handler) { + private void registerRestEndpoint(@Nonnull HttpString method, @Nonnull UriPath path, @Nonnull RestEndpointHandler handler) { final CorsEndpoint corsEndpoint = corsEndpoints.computeIfAbsent(path, p -> new CorsEndpoint(restConfig)); corsEndpoint.addMetadataFromHandler(handler); diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/Rest.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/Rest.java index f79dc3f5f..89aa8af3a 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/Rest.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/Rest.java @@ -40,5 +40,5 @@ public record Rest(@Nonnull OpenAPI openApi, @Nonnull List endpoints) public record Endpoint(@Nonnull UriPath path, @Nonnull HttpString method, - @Nonnull RestEndpointHandler handler) {} + @Nonnull RestEndpointHandler handler) {} } diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/CollectionsHandler.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/CollectionsHandler.java index 7bb6e9eae..478ecd67a 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/CollectionsHandler.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/CollectionsHandler.java @@ -43,7 +43,7 @@ * * @author Martin Veska (veska@fg.cz), FG Forrest a.s. (c) 2022 */ -public class CollectionsHandler extends JsonRestHandler, CatalogRestHandlingContext> { +public class CollectionsHandler extends JsonRestHandler { public CollectionsHandler(@Nonnull CatalogRestHandlingContext restHandlingContext) { super(restHandlingContext); @@ -51,7 +51,7 @@ public CollectionsHandler(@Nonnull CatalogRestHandlingContext restHandlingContex @Nonnull @Override - protected EndpointResponse> doHandleRequest(@Nonnull RestEndpointExchange exchange) { + protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { final Map parametersFromRequest = getParametersFromRequest(exchange); final Boolean withCounts = (Boolean) parametersFromRequest.get(CollectionsEndpointHeaderDescriptor.ENTITY_COUNT.name()); @@ -64,7 +64,7 @@ protected EndpointResponse> doHandleRequest(@Nonnull Res )) .toList(); - return new SuccessEndpointResponse<>(collections); + return new SuccessEndpointResponse(convertResultIntoSerializableObject(exchange, collections)); } @Nonnull diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/DeleteEntitiesByQueryHandler.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/DeleteEntitiesByQueryHandler.java index fa1531918..f2d4bf147 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/DeleteEntitiesByQueryHandler.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/DeleteEntitiesByQueryHandler.java @@ -23,25 +23,37 @@ package io.evitadb.externalApi.rest.api.catalog.dataApi.resolver.endpoint; +import io.evitadb.api.query.ConstraintContainer; import io.evitadb.api.query.Query; +import io.evitadb.api.query.QueryUtils; +import io.evitadb.api.query.RequireConstraint; +import io.evitadb.api.query.require.EntityFetch; +import io.evitadb.api.query.require.SeparateEntityContentRequireContainer; import io.evitadb.api.requestResponse.data.SealedEntity; import io.evitadb.externalApi.http.EndpointResponse; import io.evitadb.externalApi.http.SuccessEndpointResponse; import io.evitadb.externalApi.rest.api.catalog.dataApi.resolver.serializer.EntityJsonSerializer; +import io.evitadb.externalApi.rest.exception.RestInternalError; import io.evitadb.externalApi.rest.io.RestEndpointExchange; +import io.evitadb.utils.ArrayUtils; +import io.evitadb.utils.Assert; import io.undertow.util.Methods; import lombok.extern.slf4j.Slf4j; import javax.annotation.Nonnull; +import java.util.Optional; import java.util.Set; +import static io.evitadb.api.query.QueryConstraints.entityFetch; +import static io.evitadb.api.query.QueryConstraints.require; + /** * Handles entity list delete request by query. * * @author Martin Veska (veska@fg.cz), FG Forrest a.s. (c) 2022 */ @Slf4j -public class DeleteEntitiesByQueryHandler extends QueryOrientedEntitiesHandler { +public class DeleteEntitiesByQueryHandler extends QueryOrientedEntitiesHandler { @Nonnull private final EntityJsonSerializer entityJsonSerializer; @@ -57,13 +69,26 @@ protected boolean modifiesData() { @Override @Nonnull - protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { - final Query query = resolveQuery(exchange); + protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { + Query query = resolveQuery(exchange); + if (QueryUtils.findRequire(query, EntityFetch.class, SeparateEntityContentRequireContainer.class) == null) { + query = Query.query( + query.getCollection(), + query.getFilterBy(), + query.getOrderBy(), + require( + ArrayUtils.mergeArrays( + Optional.ofNullable(query.getRequire()).map(ConstraintContainer::getChildren).orElse(new RequireConstraint[0]), + new RequireConstraint[] { entityFetch() } + ) + ) + ); + } log.debug("Generated evitaDB query for deletion of entity list of type `{}` is `{}`.", restApiHandlingContext.getEntitySchema(), query); final SealedEntity[] deletedEntities = exchange.session().deleteSealedEntitiesAndReturnBodies(query); - return new SuccessEndpointResponse<>(deletedEntities); + return new SuccessEndpointResponse(convertResultIntoSerializableObject(exchange, deletedEntities)); } @Nonnull @@ -74,7 +99,11 @@ public Set getSupportedHttpMethods() { @Nonnull @Override - protected Object convertResultIntoSerializableObject(@Nonnull RestEndpointExchange exchange, @Nonnull SealedEntity[] deletedEntities) { - return entityJsonSerializer.serialize(deletedEntities); + protected Object convertResultIntoSerializableObject(@Nonnull RestEndpointExchange exchange, @Nonnull Object deletedEntities) { + Assert.isPremiseValid( + deletedEntities instanceof SealedEntity[], + () -> new RestInternalError("Expected SealedEntity[], but got `" + deletedEntities.getClass().getName() + "`.") + ); + return entityJsonSerializer.serialize((SealedEntity[]) deletedEntities); } } diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/DeleteEntityHandler.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/DeleteEntityHandler.java index db10bfeb0..884c2b019 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/DeleteEntityHandler.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/DeleteEntityHandler.java @@ -46,7 +46,7 @@ * @author Martin Veska (veska@fg.cz), FG Forrest a.s. (c) 2022 */ @Slf4j -public class DeleteEntityHandler extends EntityHandler { +public class DeleteEntityHandler extends EntityHandler { public DeleteEntityHandler(@Nonnull CollectionRestHandlingContext restApiHandlingContext) { super(restApiHandlingContext); @@ -59,7 +59,7 @@ protected boolean modifiesData() { @Override @Nonnull - protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { + protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { final Map parametersFromRequest = getParametersFromRequest(exchange); Assert.isTrue( @@ -75,8 +75,8 @@ protected EndpointResponse doHandleRequest(@Nonnull RestEndpointEx (Integer) parametersFromRequest.get(DeleteEntityEndpointHeaderDescriptor.PRIMARY_KEY.name()), entityContentRequires ) - .map(it -> (EndpointResponse) new SuccessEndpointResponse<>(it)) - .orElse(new NotFoundEndpointResponse<>()); + .map(it -> (EndpointResponse) new SuccessEndpointResponse(convertResultIntoSerializableObject(exchange, it))) + .orElse(new NotFoundEndpointResponse()); } @Nonnull diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/EntityHandler.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/EntityHandler.java index cd2241ae9..ae3d8d456 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/EntityHandler.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/EntityHandler.java @@ -26,8 +26,10 @@ import io.evitadb.api.requestResponse.data.EntityClassifier; import io.evitadb.externalApi.rest.api.catalog.dataApi.resolver.serializer.EntityJsonSerializer; import io.evitadb.externalApi.rest.api.catalog.resolver.endpoint.CatalogRestHandlingContext; +import io.evitadb.externalApi.rest.exception.RestInternalError; import io.evitadb.externalApi.rest.io.JsonRestHandler; import io.evitadb.externalApi.rest.io.RestEndpointExchange; +import io.evitadb.utils.Assert; import lombok.extern.slf4j.Slf4j; import javax.annotation.Nonnull; @@ -39,7 +41,7 @@ * @author Lukáš Hornych, FG Forrest a.s. (c) 2023 */ @Slf4j -public abstract class EntityHandler extends JsonRestHandler { +public abstract class EntityHandler extends JsonRestHandler { @Nonnull private final EntityJsonSerializer entityJsonSerializer; @@ -57,7 +59,11 @@ public LinkedHashSet getSupportedResponseContentTypes() { @Nonnull @Override - protected Object convertResultIntoSerializableObject(@Nonnull RestEndpointExchange exchange, @Nonnull R deletedEntity) { - return entityJsonSerializer.serialize(deletedEntity); + protected Object convertResultIntoSerializableObject(@Nonnull RestEndpointExchange exchange, @Nonnull Object deletedEntity) { + Assert.isPremiseValid( + deletedEntity instanceof EntityClassifier, + () -> new RestInternalError("Entity must be instance of EntityClassifier, but got `" + deletedEntity.getClass().getName() + "`.") + ); + return entityJsonSerializer.serialize((EntityClassifier) deletedEntity); } } diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/GetEntityHandler.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/GetEntityHandler.java index ed6eb4da9..6701937c2 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/GetEntityHandler.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/GetEntityHandler.java @@ -46,7 +46,7 @@ * @author Martin Veska (veska@fg.cz), FG Forrest a.s. (c) 2022 */ @Slf4j -public class GetEntityHandler extends EntityHandler { +public class GetEntityHandler extends EntityHandler { public GetEntityHandler(@Nonnull CollectionRestHandlingContext restApiHandlingContext) { super(restApiHandlingContext); @@ -54,7 +54,7 @@ public GetEntityHandler(@Nonnull CollectionRestHandlingContext restApiHandlingCo @Override @Nonnull - protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { + protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { final Map parametersFromRequest = getParametersFromRequest(exchange); final Query query = Query.query( @@ -67,8 +67,8 @@ protected EndpointResponse doHandleRequest(@Nonnull RestEndpoi return exchange.session() .queryOne(query, EntityClassifier.class) - .map(it -> (EndpointResponse) new SuccessEndpointResponse<>(it)) - .orElse(new NotFoundEndpointResponse<>()); + .map(it -> (EndpointResponse) new SuccessEndpointResponse(convertResultIntoSerializableObject(exchange, it))) + .orElse(new NotFoundEndpointResponse()); } @Nonnull diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/GetUnknownEntityHandler.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/GetUnknownEntityHandler.java index 618d74582..980134e31 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/GetUnknownEntityHandler.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/GetUnknownEntityHandler.java @@ -45,7 +45,7 @@ * @author Martin Veska (veska@fg.cz), FG Forrest a.s. (c) 2022 */ @Slf4j -public class GetUnknownEntityHandler extends EntityHandler { +public class GetUnknownEntityHandler extends EntityHandler { public GetUnknownEntityHandler(@Nonnull CatalogRestHandlingContext restHandlingContext) { super(restHandlingContext); @@ -53,7 +53,7 @@ public GetUnknownEntityHandler(@Nonnull CatalogRestHandlingContext restHandlingC @Override @Nonnull - protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { + protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { final Map parametersFromRequest = getParametersFromRequest(exchange); final Query query = Query.query( @@ -65,8 +65,8 @@ protected EndpointResponse doHandleRequest(@Nonnull RestEndpoi return exchange.session() .queryOne(query, EntityClassifier.class) - .map(it -> (EndpointResponse) new SuccessEndpointResponse<>(it)) - .orElse(new NotFoundEndpointResponse<>()); + .map(it -> (EndpointResponse) new SuccessEndpointResponse(convertResultIntoSerializableObject(exchange, it))) + .orElse(new NotFoundEndpointResponse()); } @Nonnull diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/ListEntitiesHandler.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/ListEntitiesHandler.java index 74f7e0826..d19f4456d 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/ListEntitiesHandler.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/ListEntitiesHandler.java @@ -28,7 +28,9 @@ import io.evitadb.externalApi.http.EndpointResponse; import io.evitadb.externalApi.http.SuccessEndpointResponse; import io.evitadb.externalApi.rest.api.catalog.dataApi.resolver.serializer.EntityJsonSerializer; +import io.evitadb.externalApi.rest.exception.RestInternalError; import io.evitadb.externalApi.rest.io.RestEndpointExchange; +import io.evitadb.utils.Assert; import lombok.extern.slf4j.Slf4j; import javax.annotation.Nonnull; @@ -40,7 +42,7 @@ * @author Martin Veska (veska@fg.cz), FG Forrest a.s. (c) 2022 */ @Slf4j -public class ListEntitiesHandler extends QueryOrientedEntitiesHandler> { +public class ListEntitiesHandler extends QueryOrientedEntitiesHandler { @Nonnull private final EntityJsonSerializer entityJsonSerializer; @@ -51,19 +53,24 @@ public ListEntitiesHandler(@Nonnull CollectionRestHandlingContext restApiHandlin @Nonnull @Override - protected EndpointResponse> doHandleRequest(@Nonnull RestEndpointExchange exchange) { + protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { final Query query = resolveQuery(exchange); log.debug("Generated evitaDB query for entity list of type `{}` is `{}`.", restApiHandlingContext.getEntitySchema(), query); final List entities = exchange.session().queryList(query, EntityClassifier.class); - return new SuccessEndpointResponse<>(entities); + return new SuccessEndpointResponse(convertResultIntoSerializableObject(exchange, entities)); } @Nonnull @Override - protected Object convertResultIntoSerializableObject(@Nonnull RestEndpointExchange exchange, @Nonnull List entities) { - return entityJsonSerializer.serialize(entities); + protected Object convertResultIntoSerializableObject(@Nonnull RestEndpointExchange exchange, @Nonnull Object entities) { + Assert.isPremiseValid( + entities instanceof List, + () -> new RestInternalError("Expected list of entities, but got `" + entities.getClass().getName() + "`.") + ); + //noinspection unchecked + return entityJsonSerializer.serialize((List) entities); } } diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/ListUnknownEntitiesHandler.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/ListUnknownEntitiesHandler.java index 29188679c..e3b9e2947 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/ListUnknownEntitiesHandler.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/ListUnknownEntitiesHandler.java @@ -31,8 +31,10 @@ import io.evitadb.externalApi.rest.api.catalog.dataApi.resolver.constraint.RequireConstraintFromRequestQueryBuilder; import io.evitadb.externalApi.rest.api.catalog.dataApi.resolver.serializer.EntityJsonSerializer; import io.evitadb.externalApi.rest.api.catalog.resolver.endpoint.CatalogRestHandlingContext; +import io.evitadb.externalApi.rest.exception.RestInternalError; import io.evitadb.externalApi.rest.io.JsonRestHandler; import io.evitadb.externalApi.rest.io.RestEndpointExchange; +import io.evitadb.utils.Assert; import io.undertow.util.Methods; import lombok.extern.slf4j.Slf4j; @@ -48,7 +50,7 @@ * @author Martin Veska (veska@fg.cz), FG Forrest a.s. (c) 2022 */ @Slf4j -public class ListUnknownEntitiesHandler extends JsonRestHandler, CatalogRestHandlingContext> { +public class ListUnknownEntitiesHandler extends JsonRestHandler { @Nonnull private final EntityJsonSerializer entityJsonSerializer; @@ -60,7 +62,7 @@ public ListUnknownEntitiesHandler(@Nonnull CatalogRestHandlingContext restHandli @Override @Nonnull - protected EndpointResponse> doHandleRequest(@Nonnull RestEndpointExchange exchange) { + protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { final Map parametersFromRequest = getParametersFromRequest(exchange); final Query query = Query.query( @@ -71,7 +73,7 @@ protected EndpointResponse> doHandleRequest(@Nonnull Rest log.debug("Generated evitaDB query for unknown entity list fetch is `{}`.", query); final List entities = exchange.session().queryList(query, EntityClassifier.class); - return new SuccessEndpointResponse<>(entities); + return new SuccessEndpointResponse(convertResultIntoSerializableObject(exchange, entities)); } @Nonnull @@ -88,7 +90,12 @@ public LinkedHashSet getSupportedResponseContentTypes() { @Nonnull @Override - protected Object convertResultIntoSerializableObject(@Nonnull RestEndpointExchange exchange, @Nonnull List entities) { - return entityJsonSerializer.serialize(entities); + protected Object convertResultIntoSerializableObject(@Nonnull RestEndpointExchange exchange, @Nonnull Object entities) { + Assert.isPremiseValid( + entities instanceof List, + () -> new RestInternalError("Expected list of entities, but got `" + entities.getClass().getName() + "`.") + ); + //noinspection unchecked + return entityJsonSerializer.serialize((List) entities); } } diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/QueryEntitiesHandler.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/QueryEntitiesHandler.java index 097d9869b..f325e6af7 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/QueryEntitiesHandler.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/QueryEntitiesHandler.java @@ -40,6 +40,7 @@ import io.evitadb.externalApi.rest.api.catalog.dataApi.resolver.serializer.ExtraResultsJsonSerializer; import io.evitadb.externalApi.rest.exception.RestInternalError; import io.evitadb.externalApi.rest.io.RestEndpointExchange; +import io.evitadb.utils.Assert; import lombok.extern.slf4j.Slf4j; import javax.annotation.Nonnull; @@ -57,7 +58,7 @@ * @author Martin Veska (veska@fg.cz), FG Forrest a.s. (c) 2022 */ @Slf4j -public class QueryEntitiesHandler extends QueryOrientedEntitiesHandler> { +public class QueryEntitiesHandler extends QueryOrientedEntitiesHandler { @Nonnull private final EntityJsonSerializer entityJsonSerializer; @Nonnull private final ExtraResultsJsonSerializer extraResultsJsonSerializer; @@ -78,23 +79,30 @@ public QueryEntitiesHandler(@Nonnull CollectionRestHandlingContext restApiHandli @Override @Nonnull - protected EndpointResponse> doHandleRequest(@Nonnull RestEndpointExchange exchange) { + protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { final Query query = resolveQuery(exchange); log.debug("Generated evitaDB query for entity query of type `{}` is `{}`.", restApiHandlingContext.getEntitySchema(), query); final EvitaResponse response = exchange.session().query(query, EntityClassifier.class); - return new SuccessEndpointResponse<>(response); + return new SuccessEndpointResponse(convertResultIntoSerializableObject(exchange, response)); } @Nonnull @Override - protected Object convertResultIntoSerializableObject(@Nonnull RestEndpointExchange exchange, @Nonnull EvitaResponse response) { + protected Object convertResultIntoSerializableObject(@Nonnull RestEndpointExchange exchange, @Nonnull Object response) { + Assert.isPremiseValid( + response instanceof EvitaResponse, + () -> new RestInternalError("Expected evitaDB response, but got `" + response.getClass().getName() + "`.") + ); + + //noinspection unchecked + final EvitaResponse evitaResponse = (EvitaResponse) response; final QueryResponseBuilder queryResponseBuilder = QueryResponse.builder() - .recordPage(serializeRecordPage(response)); - if (!response.getExtraResults().isEmpty()) { + .recordPage(serializeRecordPage(evitaResponse)); + if (!evitaResponse.getExtraResults().isEmpty()) { queryResponseBuilder - .extraResults(extraResultsJsonSerializer.serialize(response.getExtraResults(), restApiHandlingContext.getEntitySchema())); + .extraResults(extraResultsJsonSerializer.serialize(evitaResponse.getExtraResults(), restApiHandlingContext.getEntitySchema())); } return queryResponseBuilder.build(); diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/QueryOrientedEntitiesHandler.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/QueryOrientedEntitiesHandler.java index 4d8afd0e8..af9662bd8 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/QueryOrientedEntitiesHandler.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/QueryOrientedEntitiesHandler.java @@ -67,7 +67,7 @@ * @author Martin Veska (veska@fg.cz), FG Forrest a.s. (c) 2022 */ @Slf4j -public abstract class QueryOrientedEntitiesHandler extends JsonRestHandler { +public abstract class QueryOrientedEntitiesHandler extends JsonRestHandler { @Nonnull private final FilterConstraintResolver filterConstraintResolver; @Nonnull private final OrderConstraintResolver orderConstraintResolver; diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/UpsertEntityHandler.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/UpsertEntityHandler.java index 7980a7175..aecc79802 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/UpsertEntityHandler.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/dataApi/resolver/endpoint/UpsertEntityHandler.java @@ -57,7 +57,7 @@ * @author Martin Veska (veska@fg.cz), FG Forrest a.s. (c) 2022 */ @Slf4j -public class UpsertEntityHandler extends EntityHandler { +public class UpsertEntityHandler extends EntityHandler { @Nonnull private final RestEntityUpsertMutationConverter mutationResolver; @Nonnull private final RequireConstraintResolver requireConstraintResolver; @@ -85,7 +85,7 @@ protected boolean modifiesData() { @Override @Nonnull - protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { + protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { final UpsertEntityUpsertRequestDto requestData = parseRequestBody(exchange, UpsertEntityUpsertRequestDto.class); if (withPrimaryKeyInPath) { @@ -112,7 +112,7 @@ protected EndpointResponse doHandleRequest(@Nonnull RestEndpoi ? exchange.session().upsertAndFetchEntity(entityMutation, requires) : exchange.session().upsertEntity(entityMutation); - return new SuccessEndpointResponse<>(upsertedEntity); + return new SuccessEndpointResponse(convertResultIntoSerializableObject(exchange, upsertedEntity)); } @Nonnull diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/schemaApi/resolver/endpoint/CatalogSchemaHandler.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/schemaApi/resolver/endpoint/CatalogSchemaHandler.java index 25c5c8392..a35874425 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/schemaApi/resolver/endpoint/CatalogSchemaHandler.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/schemaApi/resolver/endpoint/CatalogSchemaHandler.java @@ -26,8 +26,10 @@ import io.evitadb.api.requestResponse.schema.CatalogSchemaContract; import io.evitadb.externalApi.rest.api.catalog.resolver.endpoint.CatalogRestHandlingContext; import io.evitadb.externalApi.rest.api.catalog.schemaApi.resolver.serializer.CatalogSchemaJsonSerializer; +import io.evitadb.externalApi.rest.exception.RestInternalError; import io.evitadb.externalApi.rest.io.JsonRestHandler; import io.evitadb.externalApi.rest.io.RestEndpointExchange; +import io.evitadb.utils.Assert; import lombok.extern.slf4j.Slf4j; import javax.annotation.Nonnull; @@ -39,7 +41,7 @@ * @author Lukáš Hornych, FG Forrest a.s. (c) 2023 */ @Slf4j -public abstract class CatalogSchemaHandler extends JsonRestHandler { +public abstract class CatalogSchemaHandler extends JsonRestHandler { @Nonnull private final CatalogSchemaJsonSerializer catalogSchemaJsonSerializer; @@ -57,9 +59,13 @@ public LinkedHashSet getSupportedResponseContentTypes() { @Nonnull @Override - protected Object convertResultIntoSerializableObject(@Nonnull RestEndpointExchange exchange, @Nonnull CatalogSchemaContract catalogSchema) { + protected Object convertResultIntoSerializableObject(@Nonnull RestEndpointExchange exchange, @Nonnull Object catalogSchema) { + Assert.isPremiseValid( + catalogSchema instanceof CatalogSchemaContract, + () -> new RestInternalError("Expected CatalogSchemaContract, but got `" + catalogSchema.getClass().getName() + "`.") + ); return catalogSchemaJsonSerializer.serialize( - catalogSchema, + (CatalogSchemaContract) catalogSchema, exchange.session()::getEntitySchemaOrThrow, exchange.session().getAllEntityTypes() ); diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/schemaApi/resolver/endpoint/EntitySchemaHandler.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/schemaApi/resolver/endpoint/EntitySchemaHandler.java index f964c104b..eeeec754e 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/schemaApi/resolver/endpoint/EntitySchemaHandler.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/schemaApi/resolver/endpoint/EntitySchemaHandler.java @@ -26,8 +26,10 @@ import io.evitadb.api.requestResponse.schema.EntitySchemaContract; import io.evitadb.externalApi.rest.api.catalog.dataApi.resolver.endpoint.CollectionRestHandlingContext; import io.evitadb.externalApi.rest.api.catalog.schemaApi.resolver.serializer.EntitySchemaJsonSerializer; +import io.evitadb.externalApi.rest.exception.RestInternalError; import io.evitadb.externalApi.rest.io.JsonRestHandler; import io.evitadb.externalApi.rest.io.RestEndpointExchange; +import io.evitadb.utils.Assert; import lombok.extern.slf4j.Slf4j; import javax.annotation.Nonnull; @@ -39,7 +41,7 @@ * @author Lukáš Hornych, FG Forrest a.s. (c) 2023 */ @Slf4j -public abstract class EntitySchemaHandler extends JsonRestHandler { +public abstract class EntitySchemaHandler extends JsonRestHandler { @Nonnull private final EntitySchemaJsonSerializer entitySchemaJsonSerializer; @@ -57,9 +59,13 @@ public LinkedHashSet getSupportedResponseContentTypes() { @Nonnull @Override - protected Object convertResultIntoSerializableObject(@Nonnull RestEndpointExchange exchange, @Nonnull EntitySchemaContract entitySchema) { + protected Object convertResultIntoSerializableObject(@Nonnull RestEndpointExchange exchange, @Nonnull Object entitySchema) { + Assert.isPremiseValid( + entitySchema instanceof EntitySchemaContract, + () -> new RestInternalError("Entity schema must be instance of EntitySchemaContract, but was `" + entitySchema.getClass().getName() + "`.") + ); return entitySchemaJsonSerializer.serialize( - entitySchema, + (EntitySchemaContract) entitySchema, exchange.session()::getEntitySchemaOrThrow ); } diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/schemaApi/resolver/endpoint/GetCatalogSchemaHandler.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/schemaApi/resolver/endpoint/GetCatalogSchemaHandler.java index ea5139bce..e18c9a89a 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/schemaApi/resolver/endpoint/GetCatalogSchemaHandler.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/schemaApi/resolver/endpoint/GetCatalogSchemaHandler.java @@ -48,8 +48,11 @@ public GetCatalogSchemaHandler(@Nonnull CatalogRestHandlingContext restApiHandli @Override @Nonnull - protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { - return new SuccessEndpointResponse<>(exchange.session().getCatalogSchema()); + protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { + return new SuccessEndpointResponse(convertResultIntoSerializableObject( + exchange, + exchange.session().getCatalogSchema() + )); } @Nonnull diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/schemaApi/resolver/endpoint/GetEntitySchemaHandler.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/schemaApi/resolver/endpoint/GetEntitySchemaHandler.java index 7d77e57ad..9d965bb8c 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/schemaApi/resolver/endpoint/GetEntitySchemaHandler.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/schemaApi/resolver/endpoint/GetEntitySchemaHandler.java @@ -49,10 +49,10 @@ public GetEntitySchemaHandler(@Nonnull CollectionRestHandlingContext restApiHand @Override @Nonnull - protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { + protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { return exchange.session().getEntitySchema(restApiHandlingContext.getEntityType()) - .map(it -> (EndpointResponse) new SuccessEndpointResponse<>((EntitySchemaContract) it)) - .orElse(new NotFoundEndpointResponse<>()); + .map(it -> (EndpointResponse) new SuccessEndpointResponse(convertResultIntoSerializableObject(exchange, it))) + .orElse(new NotFoundEndpointResponse()); } @Nonnull diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/schemaApi/resolver/endpoint/UpdateCatalogSchemaHandler.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/schemaApi/resolver/endpoint/UpdateCatalogSchemaHandler.java index 0009f0211..53716be67 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/schemaApi/resolver/endpoint/UpdateCatalogSchemaHandler.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/schemaApi/resolver/endpoint/UpdateCatalogSchemaHandler.java @@ -69,7 +69,7 @@ protected boolean modifiesData() { @Override @Nonnull - protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { + protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { final CreateOrUpdateEntitySchemaRequestData requestData = parseRequestBody(exchange, CreateOrUpdateEntitySchemaRequestData.class); final List schemaMutations = new LinkedList<>(); @@ -82,7 +82,7 @@ protected EndpointResponse doHandleRequest(@Nonnull RestE final CatalogSchemaContract updatedCatalogSchema = exchange.session().updateAndFetchCatalogSchema( schemaMutations.toArray(LocalCatalogSchemaMutation[]::new) ); - return new SuccessEndpointResponse<>(updatedCatalogSchema); + return new SuccessEndpointResponse(convertResultIntoSerializableObject(exchange, updatedCatalogSchema)); } @Nonnull diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/schemaApi/resolver/endpoint/UpdateEntitySchemaHandler.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/schemaApi/resolver/endpoint/UpdateEntitySchemaHandler.java index d7d78e47b..191aee436 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/schemaApi/resolver/endpoint/UpdateEntitySchemaHandler.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/catalog/schemaApi/resolver/endpoint/UpdateEntitySchemaHandler.java @@ -70,7 +70,7 @@ protected boolean modifiesData() { @Override @Nonnull - protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { + protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { final CreateOrUpdateEntitySchemaRequestData requestData = parseRequestBody(exchange, CreateOrUpdateEntitySchemaRequestData.class); final List schemaMutations = new LinkedList<>(); @@ -85,7 +85,7 @@ protected EndpointResponse doHandleRequest(@Nonnull RestEn ); final EntitySchemaContract updatedEntitySchema = exchange.session().updateAndFetchEntitySchema(entitySchemaMutation); - return new SuccessEndpointResponse<>(updatedEntitySchema); + return new SuccessEndpointResponse(convertResultIntoSerializableObject(exchange, updatedEntitySchema)); } @Nonnull diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/openApi/OpenApiCatalogEndpoint.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/openApi/OpenApiCatalogEndpoint.java index 9b43a67c5..871975a10 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/openApi/OpenApiCatalogEndpoint.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/openApi/OpenApiCatalogEndpoint.java @@ -72,7 +72,7 @@ private OpenApiCatalogEndpoint(@Nonnull CatalogSchemaContract catalogSchema, @Nonnull List parameters, @Nullable OpenApiSimpleType requestBody, @Nonnull OpenApiSimpleType successResponse, - @Nonnull Function> handlerBuilder) { + @Nonnull Function> handlerBuilder) { super(method, path, localized, operationId, description, deprecationNotice, parameters, requestBody, successResponse, handlerBuilder); this.catalogSchema = catalogSchema; } @@ -87,7 +87,7 @@ public static Builder newCatalogEndpoint(@Nonnull CatalogSchemaContract catalogS @Nonnull @Override - public RestEndpointHandler toHandler(@Nonnull ObjectMapper objectMapper, + public RestEndpointHandler toHandler(@Nonnull ObjectMapper objectMapper, @Nonnull Evita evita, @Nonnull OpenAPI openApi, @Nonnull Map>> enumMapping) { @@ -119,7 +119,7 @@ public static class Builder { @Nullable private OpenApiSimpleType requestBody; @Nullable private OpenApiSimpleType successResponse; - @Nullable private Function> handlerBuilder; + @Nullable private Function> handlerBuilder; private Builder(@Nonnull CatalogSchemaContract catalogSchema) { this.catalogSchema = catalogSchema; @@ -246,7 +246,7 @@ public Builder successResponse(@Nonnull OpenApiSimpleType successResponseType) { * Sets handler builder. */ @Nonnull - public Builder handler(@Nonnull Function> handlerBuilder) { + public Builder handler(@Nonnull Function> handlerBuilder) { this.handlerBuilder = handlerBuilder; return this; } diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/openApi/OpenApiCollectionEndpoint.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/openApi/OpenApiCollectionEndpoint.java index 4cbd310d6..8401b84f4 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/openApi/OpenApiCollectionEndpoint.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/openApi/OpenApiCollectionEndpoint.java @@ -77,7 +77,7 @@ private OpenApiCollectionEndpoint(@Nonnull CatalogSchemaContract catalogSchema, @Nonnull List parameters, @Nullable OpenApiSimpleType requestBody, @Nonnull OpenApiSimpleType successResponse, - @Nonnull Function> handlerBuilder) { + @Nonnull Function> handlerBuilder) { super(method, path, localized, operationId, description, deprecationNotice, parameters, requestBody, successResponse, handlerBuilder); this.catalogSchema = catalogSchema; this.entitySchema = entitySchema; @@ -94,7 +94,7 @@ public static Builder newCollectionEndpoint(@Nonnull CatalogSchemaContract catal @Nonnull @Override - public RestEndpointHandler toHandler(@Nonnull ObjectMapper objectMapper, + public RestEndpointHandler toHandler(@Nonnull ObjectMapper objectMapper, @Nonnull Evita evita, @Nonnull OpenAPI openApi, @Nonnull Map>> enumMapping) { @@ -129,7 +129,7 @@ public static class Builder { @Nullable private OpenApiSimpleType requestBody; @Nullable private OpenApiSimpleType successResponse; - @Nullable private Function> handlerBuilder; + @Nullable private Function> handlerBuilder; private Builder(@Nonnull CatalogSchemaContract catalogSchema, @Nonnull EntitySchemaContract entitySchema) { this.catalogSchema = catalogSchema; @@ -258,7 +258,7 @@ public Builder successResponse(@Nonnull OpenApiSimpleType successResponseType) { * Sets handler builder. */ @Nonnull - public Builder handler(@Nonnull Function> handlerBuilder) { + public Builder handler(@Nonnull Function> handlerBuilder) { this.handlerBuilder = handlerBuilder; return this; } diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/openApi/OpenApiEndpoint.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/openApi/OpenApiEndpoint.java index dc5fc80a2..d9e1e1128 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/openApi/OpenApiEndpoint.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/openApi/OpenApiEndpoint.java @@ -103,7 +103,7 @@ public abstract class OpenApiEndpoint { @Nullable protected final OpenApiSimpleType requestBody; @Nullable protected final OpenApiSimpleType successResponse; - @Nonnull protected final Function> handlerBuilder; + @Nonnull protected final Function> handlerBuilder; /** * Instantiate a new handler for this particular endpoint with passed data. @@ -115,7 +115,7 @@ public abstract class OpenApiEndpoint { * @return ready-to-handle endpoint handler */ @Nonnull - public abstract RestEndpointHandler toHandler(@Nonnull ObjectMapper objectMapper, + public abstract RestEndpointHandler toHandler(@Nonnull ObjectMapper objectMapper, @Nonnull Evita evita, @Nonnull OpenAPI openApi, @Nonnull Map>> enumMapping); diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/openApi/OpenApiSystemEndpoint.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/openApi/OpenApiSystemEndpoint.java index c950cb94e..78fda0661 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/openApi/OpenApiSystemEndpoint.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/openApi/OpenApiSystemEndpoint.java @@ -65,7 +65,7 @@ private OpenApiSystemEndpoint(@Nonnull PathItem.HttpMethod method, @Nonnull List parameters, @Nullable OpenApiSimpleType requestBody, @Nonnull OpenApiSimpleType successResponse, - @Nonnull Function> handlerBuilder) { + @Nonnull Function> handlerBuilder) { super(method, path, false, operationId, description, deprecationNotice, parameters, requestBody, successResponse, handlerBuilder); } @@ -79,7 +79,7 @@ public static Builder newSystemEndpoint() { @Nonnull @Override - public RestEndpointHandler toHandler(@Nonnull ObjectMapper objectMapper, + public RestEndpointHandler toHandler(@Nonnull ObjectMapper objectMapper, @Nonnull Evita evita, @Nonnull OpenAPI openApi, @Nonnull Map>> enumMapping) { @@ -107,7 +107,7 @@ public static class Builder { @Nullable private OpenApiSimpleType requestBody; @Nullable private OpenApiSimpleType successResponse; - @Nullable private Function> handlerBuilder; + @Nullable private Function> handlerBuilder; private Builder() { this.parameters = new LinkedList<>(); @@ -215,7 +215,7 @@ public Builder successResponse(@Nonnull OpenApiSimpleType successResponseType) { * Sets handler builder. */ @Nonnull - public Builder handler(@Nonnull Function> handlerBuilder) { + public Builder handler(@Nonnull Function> handlerBuilder) { this.handlerBuilder = handlerBuilder; return this; } diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/resolver/endpoint/OpenApiSpecificationHandler.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/resolver/endpoint/OpenApiSpecificationHandler.java index d30d27ad6..eebebfd9f 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/resolver/endpoint/OpenApiSpecificationHandler.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/resolver/endpoint/OpenApiSpecificationHandler.java @@ -30,7 +30,6 @@ import io.evitadb.externalApi.rest.io.RestEndpointExchange; import io.evitadb.externalApi.rest.io.RestEndpointHandler; import io.evitadb.externalApi.rest.io.RestHandlingContext; -import io.swagger.v3.oas.models.OpenAPI; import io.undertow.util.Methods; import javax.annotation.Nonnull; @@ -46,7 +45,7 @@ * * @author Martin Veska (veska@fg.cz), FG Forrest a.s. (c) 2022 */ -public class OpenApiSpecificationHandler extends RestEndpointHandler { +public class OpenApiSpecificationHandler extends RestEndpointHandler { public OpenApiSpecificationHandler(@Nonnull C restHandlingContext) { super(restHandlingContext); @@ -54,8 +53,8 @@ public OpenApiSpecificationHandler(@Nonnull C restHandlingContext) { @Nonnull @Override - protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { - return new SuccessEndpointResponse<>(restApiHandlingContext.getOpenApi()); + protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { + return new SuccessEndpointResponse(restApiHandlingContext.getOpenApi()); } @Nonnull @@ -74,7 +73,7 @@ public LinkedHashSet getSupportedResponseContentTypes() { } @Override - protected void writeResult(@Nonnull RestEndpointExchange exchange, @Nonnull OutputStream outputStream, @Nonnull OpenAPI openApiSpecification) { + protected void writeResult(@Nonnull RestEndpointExchange exchange, @Nonnull OutputStream outputStream, @Nonnull Object openApiSpecification) { final String preferredResponseMediaType = exchange.preferredResponseContentType(); try { if (preferredResponseMediaType.equals(MimeTypes.APPLICATION_YAML)) { diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/system/resolver/endpoint/CatalogHandler.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/system/resolver/endpoint/CatalogHandler.java index bee0cd85f..48dbd0b9c 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/system/resolver/endpoint/CatalogHandler.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/system/resolver/endpoint/CatalogHandler.java @@ -25,8 +25,10 @@ import io.evitadb.api.CatalogContract; import io.evitadb.externalApi.rest.api.system.resolver.serializer.CatalogJsonSerializer; +import io.evitadb.externalApi.rest.exception.RestInternalError; import io.evitadb.externalApi.rest.io.JsonRestHandler; import io.evitadb.externalApi.rest.io.RestEndpointExchange; +import io.evitadb.utils.Assert; import javax.annotation.Nonnull; import java.util.LinkedHashSet; @@ -36,7 +38,7 @@ * * @author Lukáš Hornych, FG Forrest a.s. (c) 2023 */ -public abstract class CatalogHandler extends JsonRestHandler { +public abstract class CatalogHandler extends JsonRestHandler { @Nonnull private final CatalogJsonSerializer catalogJsonSerializer; @@ -54,7 +56,11 @@ public LinkedHashSet getSupportedResponseContentTypes() { @Nonnull @Override - protected Object convertResultIntoSerializableObject(@Nonnull RestEndpointExchange exchange, @Nonnull CatalogContract catalog) { - return catalogJsonSerializer.serialize(catalog); + protected Object convertResultIntoSerializableObject(@Nonnull RestEndpointExchange exchange, @Nonnull Object catalog) { + Assert.isPremiseValid( + catalog instanceof CatalogContract, + () -> new RestInternalError("Catalog should be instance of CatalogContract, but was `" + catalog.getClass().getName() + "`.") + ); + return catalogJsonSerializer.serialize((CatalogContract) catalog); } } diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/system/resolver/endpoint/CreateCatalogHandler.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/system/resolver/endpoint/CreateCatalogHandler.java index fa0a8ecf4..faf80d536 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/system/resolver/endpoint/CreateCatalogHandler.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/system/resolver/endpoint/CreateCatalogHandler.java @@ -51,13 +51,13 @@ protected boolean modifiesData() { @Nonnull @Override - protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { + protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { final CreateCatalogRequestDto requestBody = parseRequestBody(exchange, CreateCatalogRequestDto.class); restApiHandlingContext.getEvita().defineCatalog(requestBody.name()); final CatalogContract newCatalog = restApiHandlingContext.getEvita().getCatalogInstanceOrThrowException(requestBody.name()); - return new SuccessEndpointResponse<>(newCatalog); + return new SuccessEndpointResponse(convertResultIntoSerializableObject(exchange, newCatalog)); } @Nonnull diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/system/resolver/endpoint/DeleteCatalogHandler.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/system/resolver/endpoint/DeleteCatalogHandler.java index 7a5b01c72..4a8d6c549 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/system/resolver/endpoint/DeleteCatalogHandler.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/system/resolver/endpoint/DeleteCatalogHandler.java @@ -45,7 +45,7 @@ * * @author Lukáš Hornych, FG Forrest a.s. (c) 2023 */ -public class DeleteCatalogHandler extends JsonRestHandler { +public class DeleteCatalogHandler extends JsonRestHandler { public DeleteCatalogHandler(@Nonnull SystemRestHandlingContext restApiHandlingContext) { super(restApiHandlingContext); @@ -58,13 +58,13 @@ protected boolean modifiesData() { @Nonnull @Override - protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { + protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { final Map parameters = getParametersFromRequest(exchange); final String catalogName = (String) parameters.get(CatalogsHeaderDescriptor.NAME.name()); final Optional catalog = restApiHandlingContext.getEvita().getCatalogInstance(catalogName); if (catalog.isEmpty()) { - return new NotFoundEndpointResponse<>(); + return new NotFoundEndpointResponse(); } final boolean deleted = restApiHandlingContext.getEvita().deleteCatalogIfExists(catalog.get().getName()); @@ -72,7 +72,7 @@ protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange e deleted, () -> new RestInternalError("Could not delete catalog `" + catalog.get().getName() + "`, even though it should exist.") ); - return new SuccessEndpointResponse<>(); + return new SuccessEndpointResponse(); } @Nonnull diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/system/resolver/endpoint/GetCatalogHandler.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/system/resolver/endpoint/GetCatalogHandler.java index 5e1de32a0..a505ef69f 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/system/resolver/endpoint/GetCatalogHandler.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/system/resolver/endpoint/GetCatalogHandler.java @@ -49,12 +49,12 @@ public GetCatalogHandler(@Nonnull SystemRestHandlingContext restApiHandlingConte @Nonnull @Override - protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { + protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { final Map parameters = getParametersFromRequest(exchange); final String catalogName = (String) parameters.get(CatalogsHeaderDescriptor.NAME.name()); return restApiHandlingContext.getEvita().getCatalogInstance(catalogName) - .map(it -> (EndpointResponse) new SuccessEndpointResponse<>(it)) - .orElse(new NotFoundEndpointResponse<>()); + .map(it -> (EndpointResponse) new SuccessEndpointResponse(convertResultIntoSerializableObject(exchange, it))) + .orElse(new NotFoundEndpointResponse()); } @Nonnull diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/system/resolver/endpoint/ListCatalogsHandler.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/system/resolver/endpoint/ListCatalogsHandler.java index 4e052ee99..d7bfab031 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/system/resolver/endpoint/ListCatalogsHandler.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/system/resolver/endpoint/ListCatalogsHandler.java @@ -27,8 +27,10 @@ import io.evitadb.externalApi.http.EndpointResponse; import io.evitadb.externalApi.http.SuccessEndpointResponse; import io.evitadb.externalApi.rest.api.system.resolver.serializer.CatalogJsonSerializer; +import io.evitadb.externalApi.rest.exception.RestInternalError; import io.evitadb.externalApi.rest.io.JsonRestHandler; import io.evitadb.externalApi.rest.io.RestEndpointExchange; +import io.evitadb.utils.Assert; import io.undertow.util.Methods; import javax.annotation.Nonnull; @@ -41,7 +43,7 @@ * * @author Lukáš Hornych, FG Forrest a.s. (c) 2023 */ -public class ListCatalogsHandler extends JsonRestHandler, SystemRestHandlingContext> { +public class ListCatalogsHandler extends JsonRestHandler { @Nonnull private final CatalogJsonSerializer catalogJsonSerializer; @@ -53,9 +55,9 @@ public ListCatalogsHandler(@Nonnull SystemRestHandlingContext restApiHandlingCon @Nonnull @Override - protected EndpointResponse> doHandleRequest(@Nonnull RestEndpointExchange exchange) { + protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { final Collection catalogs = restApiHandlingContext.getEvita().getCatalogs(); - return new SuccessEndpointResponse<>(catalogs); + return new SuccessEndpointResponse(convertResultIntoSerializableObject(exchange, catalogs)); } @Nonnull @@ -72,7 +74,13 @@ public LinkedHashSet getSupportedResponseContentTypes() { @Nonnull @Override - protected Object convertResultIntoSerializableObject(@Nonnull RestEndpointExchange exchange, @Nonnull Collection catalogs) { - return catalogJsonSerializer.serialize(catalogs); + protected Object convertResultIntoSerializableObject(@Nonnull RestEndpointExchange exchange, @Nonnull Object catalogs) { + // Collection + Assert.isPremiseValid( + catalogs instanceof Collection, + () -> new RestInternalError("Expected collection of catalogs, but got `" + catalogs.getClass().getName() + "`.") + ); + //noinspection unchecked + return catalogJsonSerializer.serialize((Collection) catalogs); } } diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/system/resolver/endpoint/LivenessHandler.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/system/resolver/endpoint/LivenessHandler.java index cef95aff1..1acf191f6 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/system/resolver/endpoint/LivenessHandler.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/system/resolver/endpoint/LivenessHandler.java @@ -39,7 +39,7 @@ * * @author Lukáš Hornych, FG Forrest a.s. (c) 2023 */ -public class LivenessHandler extends JsonRestHandler { +public class LivenessHandler extends JsonRestHandler { public LivenessHandler(@Nonnull SystemRestHandlingContext restApiHandlingContext) { super(restApiHandlingContext); @@ -47,8 +47,8 @@ public LivenessHandler(@Nonnull SystemRestHandlingContext restApiHandlingContext @Nonnull @Override - protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { - return new SuccessEndpointResponse<>(new LivenessDto(true)); + protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { + return new SuccessEndpointResponse(new LivenessDto(true)); } @Nonnull diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/system/resolver/endpoint/UpdateCatalogHandler.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/system/resolver/endpoint/UpdateCatalogHandler.java index 25b0b509c..afdb51ea4 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/system/resolver/endpoint/UpdateCatalogHandler.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/api/system/resolver/endpoint/UpdateCatalogHandler.java @@ -60,14 +60,14 @@ protected boolean modifiesData() { @Nonnull @Override - protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { + protected EndpointResponse doHandleRequest(@Nonnull RestEndpointExchange exchange) { final Map parameters = getParametersFromRequest(exchange); final UpdateCatalogRequestDto requestBody = parseRequestBody(exchange, UpdateCatalogRequestDto.class); final String catalogName = (String) parameters.get(CatalogsHeaderDescriptor.NAME.name()); final Optional catalog = restApiHandlingContext.getEvita().getCatalogInstance(catalogName); if (catalog.isEmpty()) { - return new NotFoundEndpointResponse<>(); + return new NotFoundEndpointResponse(); } final Optional newCatalogName = renameCatalog(catalog.get(), requestBody); @@ -75,8 +75,8 @@ protected EndpointResponse doHandleRequest(@Nonnull RestEndpoin final String nameOfUpdateCatalog = newCatalogName.orElse(catalogName); final CatalogContract updatedCatalog = restApiHandlingContext.getEvita().getCatalogInstance(nameOfUpdateCatalog) - .orElseThrow(() -> new RestInternalError("Couldn't find updated catalog `" + nameOfUpdateCatalog + "`")); - return new SuccessEndpointResponse<>(updatedCatalog); + .orElseThrow(() -> new RestInternalError("Couldn't find updated catalog `" + nameOfUpdateCatalog + "`")); + return new SuccessEndpointResponse(convertResultIntoSerializableObject(exchange, updatedCatalog)); } @Nonnull diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/io/CorsEndpoint.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/io/CorsEndpoint.java index f767adff8..55bc7835d 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/io/CorsEndpoint.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/io/CorsEndpoint.java @@ -50,7 +50,7 @@ public CorsEndpoint(@Nonnull ApiWithOriginControl restConfig) { this.allowedOrigins = restConfig.getAllowedOrigins() == null ? null : Set.of(restConfig.getAllowedOrigins()); } - public void addMetadataFromHandler(@Nonnull RestEndpointHandler handler) { + public void addMetadataFromHandler(@Nonnull RestEndpointHandler handler) { addMetadata( handler.getSupportedHttpMethods(), !handler.getSupportedRequestContentTypes().isEmpty(), diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/io/JsonRestHandler.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/io/JsonRestHandler.java index 0518c89ce..c0f2c60db 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/io/JsonRestHandler.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/io/JsonRestHandler.java @@ -41,7 +41,7 @@ * * @author Lukáš Hornych, FG Forrest a.s. (c) 2023 */ -public abstract class JsonRestHandler extends RestEndpointHandler { +public abstract class JsonRestHandler extends RestEndpointHandler { protected static final LinkedHashSet DEFAULT_SUPPORTED_CONTENT_TYPES = new LinkedHashSet<>(List.of(MimeTypes.APPLICATION_JSON)); @@ -68,10 +68,9 @@ protected T parseRequestBody(@Nonnull RestEndpointExchange exchange, @Nonnul } @Override - protected void writeResult(@Nonnull RestEndpointExchange exchange, @Nonnull OutputStream outputStream, @Nonnull R result) { + protected void writeResult(@Nonnull RestEndpointExchange exchange, @Nonnull OutputStream outputStream, @Nonnull Object result) { try { - final Object serializableResult = convertResultIntoSerializableObject(exchange, result); - restApiHandlingContext.getObjectMapper().writeValue(outputStream, serializableResult); + restApiHandlingContext.getObjectMapper().writeValue(outputStream, result); } catch (IOException e) { throw new OpenApiInternalError( "Could not serialize Java object response to JSON: " + e.getMessage(), @@ -89,7 +88,7 @@ protected void writeResult(@Nonnull RestEndpointExchange exchange, @Nonnull Outp * @return result object read to be serialized */ @Nonnull - protected Object convertResultIntoSerializableObject(@Nonnull RestEndpointExchange exchange, @Nonnull R result) { + protected Object convertResultIntoSerializableObject(@Nonnull RestEndpointExchange exchange, @Nonnull Object result) { return result; } } diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/io/RestEndpointExchange.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/io/RestEndpointExchange.java index 3f8aa5c7c..d4a035b9e 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/io/RestEndpointExchange.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/io/RestEndpointExchange.java @@ -70,9 +70,25 @@ public void session(@Nonnull EvitaSessionContract session) { this.session == null, () -> new RestInternalError("Session cannot overwritten when already set.") ); + Assert.isPremiseValid( + this.session.isActive(), + () -> new RestInternalError("Session has been already closed. No one should access the session!") + ); this.session = session; } + /** + * Closes the current session (and transaction) if it is open. + */ + public void closeSessionIfOpen() { + if (session != null) { + if (session.isTransactionOpen()) { + session.closeTransaction(); + } + session.close(); + } + } + @Nonnull @Override public String httpMethod() { @@ -93,8 +109,7 @@ public String preferredResponseContentType() { @Override public void close() { - if (session != null) { - session.close(); - } + // the session may not be properly closed in case of exception during request handling + closeSessionIfOpen(); } } diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/io/RestEndpointHandler.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/io/RestEndpointHandler.java index a23a7ed56..1aed209c4 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/io/RestEndpointHandler.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/io/RestEndpointHandler.java @@ -23,12 +23,14 @@ package io.evitadb.externalApi.rest.io; +import io.evitadb.api.CatalogContract; import io.evitadb.api.EvitaSessionContract; import io.evitadb.core.Evita; import io.evitadb.externalApi.exception.ExternalApiInternalError; import io.evitadb.externalApi.exception.ExternalApiInvalidUsageException; import io.evitadb.externalApi.http.EndpointExchange; import io.evitadb.externalApi.http.EndpointHandler; +import io.evitadb.externalApi.http.EndpointResponse; import io.evitadb.externalApi.rest.api.catalog.resolver.endpoint.CatalogRestHandlingContext; import io.evitadb.externalApi.rest.api.openApi.SchemaUtils; import io.evitadb.externalApi.rest.api.resolver.serializer.DataDeserializer; @@ -46,7 +48,6 @@ import java.util.Deque; import java.util.HashMap; import java.util.Map; -import java.util.NoSuchElementException; import java.util.Optional; import static io.evitadb.utils.CollectionUtils.createHashMap; @@ -57,7 +58,7 @@ * @author Martin Veska (veska@fg.cz), FG Forrest a.s. (c) 2022 */ @Slf4j -public abstract class RestEndpointHandler extends EndpointHandler { +public abstract class RestEndpointHandler extends EndpointHandler { private static final String CLIENT_ID_HEADER = "X-EvitaDB-ClientID"; private static final String REQUEST_ID_HEADER = "X-EvitaDB-RequestID"; @@ -115,6 +116,12 @@ protected void beforeRequestHandled(@Nonnull RestEndpointExchange exchange) { createSession(exchange).ifPresent(exchange::session); } + @Override + protected void afterRequestHandled(@Nonnull RestEndpointExchange exchange, @Nonnull EndpointResponse response) { + // we need to close a current session and commit changes before we send the response to client + exchange.closeSessionIfOpen(); + } + /** * Tries to create a {@link EvitaSessionContract} automatically from context. */ @@ -128,7 +135,13 @@ protected Optional createSession(@Nonnull RestEndpointExch final Evita evita = restApiHandlingContext.getEvita(); final String catalogName = catalogRestHandlingContext.getCatalogSchema().getName(); if (modifiesData()) { - return Optional.of(evita.createReadWriteSession(catalogName)); + final EvitaSessionContract session = evita.createReadWriteSession(catalogName); + final CatalogContract catalog = evita.getCatalogInstance(catalogName) + .orElseThrow(() -> new RestInternalError("Catalog `" + catalogName + "` could not be found.")); + if (catalog.supportsTransaction()) { + session.openTransaction(); + } + return Optional.of(session); } else { return Optional.of(evita.createReadOnlySession(catalogName)); } From 024e8c8affc3425f5e5c0762dc3ad7dfed4b1028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Wed, 13 Dec 2023 09:56:47 +0100 Subject: [PATCH 36/52] fix(rest-api): REST API doesn't use transactions in read-write sessions This happened when we moved from using updateCatalog/queryCatalog methods to manual session handling, but we forgot to handle transactions as well. --- .../evitadb/externalApi/rest/io/RestEndpointExchange.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/io/RestEndpointExchange.java b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/io/RestEndpointExchange.java index d4a035b9e..ca0e58284 100644 --- a/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/io/RestEndpointExchange.java +++ b/evita_external_api/evita_external_api_rest/src/main/java/io/evitadb/externalApi/rest/io/RestEndpointExchange.java @@ -59,6 +59,10 @@ public EvitaSessionContract session() { session != null, () -> new RestInternalError("Session is not available for this exchange.") ); + Assert.isPremiseValid( + this.session.isActive(), + () -> new RestInternalError("Session has been already closed. No one should access the session!") + ); return session; } @@ -70,10 +74,6 @@ public void session(@Nonnull EvitaSessionContract session) { this.session == null, () -> new RestInternalError("Session cannot overwritten when already set.") ); - Assert.isPremiseValid( - this.session.isActive(), - () -> new RestInternalError("Session has been already closed. No one should access the session!") - ); this.session = session; } From 15b142f95618cfed3970a86c78ad605a825c81da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Wed, 13 Dec 2023 09:58:22 +0100 Subject: [PATCH 37/52] docs(#200): REST example fixes, support for multiple requests in single file --- .../en/get-started/create-first-database.md | 8 +-- .../example/create-first-entity.rest | 2 +- .../example/create-small-dataset.rest | 42 ++++++++++---- .../example/define-schema-for-catalog.rest | 6 +- .../example/delete-entity-by-pk.rest | 2 +- .../example/delete-entity-by-query.rest | 2 +- .../example/filter-order-entities.rest | 2 +- .../filter-order-products-by-price.rest | 4 +- .../en/get-started/example/list-entities.rest | 2 +- .../example/query-demo-server.rest | 1 - .../example/read-entity-by-pk.rest | 2 +- .../en/get-started/example/update-entity.rest | 2 +- .../en/use/api/example/create-new-entity.rest | 4 +- .../api/example/delete-entities-by-query.rest | 2 +- .../use/api/example/evita-query-example.rest | 8 +-- .../example/finalization-of-warmup-mode.rest | 2 +- .../imperative-catalog-schema-definition.rest | 6 +- ...perative-collection-schema-definition.rest | 2 +- .../api/example/rest-full-query-example.rest | 10 ++-- .../api/example/rest-get-query-example.rest | 2 +- .../api/example/rest-list-query-example.rest | 10 ++-- .../api/example/update-existing-entity.rest | 2 +- documentation/user/en/use/api/schema-api.md | 6 +- documentation/user/en/use/api/write-data.md | 4 +- .../documentation/UserDocumentationTest.java | 2 +- .../documentation/rest/RestExecutable.java | 57 ++++++++++++++----- 26 files changed, 119 insertions(+), 73 deletions(-) diff --git a/documentation/user/en/get-started/create-first-database.md b/documentation/user/en/get-started/create-first-database.md index cc583ee97..174f7ecc8 100644 --- a/documentation/user/en/get-started/create-first-database.md +++ b/documentation/user/en/get-started/create-first-database.md @@ -73,7 +73,7 @@ Now you can use the [system API](/documentation/user/en/use/connectors/graphql.m and fill it with new predefined schemas for multiple collections: `Brand`, `Category` and `Product` by modifying its schema via the [catalog schema API](/documentation/user/en/use/connectors/graphql.md#graphql-api-instances) at URL -`https://your-server:5555/gql/test-catalog/schema`. Each collection +`https://your-server:5555/gql/evita/schema`. Each collection contains some attributes (either localized or non-localized), category is marked as a hierarchical entity that forms a tree, product is enabled to have prices: @@ -98,7 +98,7 @@ Now you can use the [system API](/documentation/user/en/use/connectors/rest.md#r and fill it with new predefined schemas for multiple collections: `Brand`, `Category` and `Product` by modifying its schema via the [catalog schema API](/documentation/user/en/use/connectors/rest.md#rest-api-instances) at URL -`https://your-server:5555/rest/test-catalog/schema`. Each collection +`https://your-server:5555/rest/evita/schema`. Each collection contains some attributes (either localized or non-localized), category is marked as a hierarchical entity that forms a tree, product is enabled to have prices: @@ -140,7 +140,7 @@ Let's see how you can retrieve the entity you just created in another read-only Once the catalog is created and the schema is known, you can insert a first entity to the catalog via the [catalog data API](/documentation/user/en/use/connectors/graphql.md#graphql-api-instances) at the -`https://your-server:5555/gql/test-catalog` URL: +`https://your-server:5555/gql/evita` URL: @@ -168,7 +168,7 @@ as mentioned above. Once the catalog is created and the schema is known, you can insert a first entity to the catalog via the [catalog data API](/documentation/user/en/use/connectors/rest.md#rest-api-instances) at the URL -`https://your-server:5555/rest/test-catalog/brand`: +`https://your-server:5555/rest/evita/brand`: diff --git a/documentation/user/en/get-started/example/create-first-entity.rest b/documentation/user/en/get-started/example/create-first-entity.rest index bba32542a..f93ed85d6 100644 --- a/documentation/user/en/get-started/example/create-first-entity.rest +++ b/documentation/user/en/get-started/example/create-first-entity.rest @@ -1,4 +1,4 @@ -PUT /rest/test-catalog/brand/1 +PUT /rest/evita/Brand/1 { "entityExistence": "MUST_NOT_EXIST", diff --git a/documentation/user/en/get-started/example/create-small-dataset.rest b/documentation/user/en/get-started/example/create-small-dataset.rest index e783d3a90..9f65a4989 100644 --- a/documentation/user/en/get-started/example/create-small-dataset.rest +++ b/documentation/user/en/get-started/example/create-small-dataset.rest @@ -1,4 +1,4 @@ -PUT /rest/test-catalog/brand/2 +PUT /rest/evita/Brand/2 { "entityExistence": "MUST_NOT_EXIST", @@ -12,7 +12,9 @@ PUT /rest/test-catalog/brand/2 ] } -PUT /rest/test-catalog/brand/3 +--- + +PUT /rest/evita/Brand/3 { "entityExistence": "MUST_NOT_EXIST", @@ -26,7 +28,9 @@ PUT /rest/test-catalog/brand/3 ] } -PUT /rest/test-catalog/brand/4 +--- + +PUT /rest/evita/Bran/4 { "entityExistence": "MUST_NOT_EXIST", @@ -40,7 +44,9 @@ PUT /rest/test-catalog/brand/4 ] } -PUT /rest/test-catalog/category/1 +--- + +PUT /rest/evita/Category/1 { "entityExistence": "MUST_NOT_EXIST", @@ -62,7 +68,9 @@ PUT /rest/test-catalog/category/1 ] } -PUT /rest/test-catalog/category/2 +--- + +PUT /rest/evita/Category/2 { "entityExistence": "MUST_NOT_EXIST", @@ -84,7 +92,9 @@ PUT /rest/test-catalog/category/2 ] } -PUT /rest/test-catalog/category/3 +--- + +PUT /rest/evita/Category/3 { "entityExistence": "MUST_NOT_EXIST", @@ -106,7 +116,9 @@ PUT /rest/test-catalog/category/3 ] } -POST /rest/test-catalog/product +--- + +POST /rest/evita/Product { "entityExistence": "MUST_NOT_EXIST", @@ -187,7 +199,9 @@ POST /rest/test-catalog/product ] } -POST /rest/test-catalog/product +--- + +POST /rest/evita/Product { "entityExistence": "MUST_NOT_EXIST", @@ -270,7 +284,9 @@ POST /rest/test-catalog/product ] } -POST /rest/test-catalog/product +--- + +POST /rest/evita/Product { "entityExistence": "MUST_NOT_EXIST", @@ -352,7 +368,9 @@ POST /rest/test-catalog/product ] } -POST /rest/test-catalog/product +--- + +POST /rest/evita/Product { "entityExistence": "MUST_NOT_EXIST", @@ -433,7 +451,9 @@ POST /rest/test-catalog/product ] } -POST /rest/test-catalog/product +--- + +POST /rest/evita/Product { "entityExistence": "MUST_NOT_EXIST", diff --git a/documentation/user/en/get-started/example/define-schema-for-catalog.rest b/documentation/user/en/get-started/example/define-schema-for-catalog.rest index f5c4ddb47..add6011fa 100644 --- a/documentation/user/en/get-started/example/define-schema-for-catalog.rest +++ b/documentation/user/en/get-started/example/define-schema-for-catalog.rest @@ -1,4 +1,4 @@ -PUT /rest/test-catalog/schema +PUT /rest/evita/schema { "mutations": [ @@ -145,7 +145,7 @@ PUT /rest/test-catalog/schema "referencedEntityTypeManaged": true, "cardinality": "ZERO_OR_ONE", "description": "Reference to the brand or manufacturer of the product.", - "filterable": true, + "indexed": true, "faceted": true } }, @@ -156,7 +156,7 @@ PUT /rest/test-catalog/schema "referencedEntityTypeManaged": true, "cardinality": "ZERO_OR_MORE", "description": "Reference to one or more categories the product is listed in.", - "filterable": true + "indexed": true } } ] diff --git a/documentation/user/en/get-started/example/delete-entity-by-pk.rest b/documentation/user/en/get-started/example/delete-entity-by-pk.rest index 02d48404b..c757b9264 100644 --- a/documentation/user/en/get-started/example/delete-entity-by-pk.rest +++ b/documentation/user/en/get-started/example/delete-entity-by-pk.rest @@ -1 +1 @@ -DELETE /rest/test-catalog/brand/1 \ No newline at end of file +DELETE /rest/evita/Brand/delete/1 \ No newline at end of file diff --git a/documentation/user/en/get-started/example/delete-entity-by-query.rest b/documentation/user/en/get-started/example/delete-entity-by-query.rest index 40a0f3cdb..36ff79dbf 100644 --- a/documentation/user/en/get-started/example/delete-entity-by-query.rest +++ b/documentation/user/en/get-started/example/delete-entity-by-query.rest @@ -1,4 +1,4 @@ -DELETE /rest/test-catalog/brand +DELETE /rest/evita/Brand { "filterBy": { diff --git a/documentation/user/en/get-started/example/filter-order-entities.rest b/documentation/user/en/get-started/example/filter-order-entities.rest index 1be5794d5..001a3de04 100644 --- a/documentation/user/en/get-started/example/filter-order-entities.rest +++ b/documentation/user/en/get-started/example/filter-order-entities.rest @@ -1,4 +1,4 @@ -POST /rest/test-catalog/brand/list +POST /rest/evita/Brand/list { "filterBy": { diff --git a/documentation/user/en/get-started/example/filter-order-products-by-price.rest b/documentation/user/en/get-started/example/filter-order-products-by-price.rest index 980167368..716fd267b 100644 --- a/documentation/user/en/get-started/example/filter-order-products-by-price.rest +++ b/documentation/user/en/get-started/example/filter-order-products-by-price.rest @@ -1,4 +1,4 @@ -POST /rest/test-catalog/product/list +POST /rest/evita/Product/list { "filterBy": { @@ -15,7 +15,7 @@ POST /rest/test-catalog/product/list "attributeContentAll": true, "associatedDataContentAll": true, "priceContentAll": true, - "referenceContentAllWithAttribtues": {}, + "referenceContentAllWithAttributes": {}, "dataInLocales": [] } } diff --git a/documentation/user/en/get-started/example/list-entities.rest b/documentation/user/en/get-started/example/list-entities.rest index 938acad9f..780e5ed29 100644 --- a/documentation/user/en/get-started/example/list-entities.rest +++ b/documentation/user/en/get-started/example/list-entities.rest @@ -1,4 +1,4 @@ -POST /rest/test-catalog/product/list +POST /rest/evita/Product/list { "filterBy": { diff --git a/documentation/user/en/get-started/example/query-demo-server.rest b/documentation/user/en/get-started/example/query-demo-server.rest index b431a31fe..c5706171b 100644 --- a/documentation/user/en/get-started/example/query-demo-server.rest +++ b/documentation/user/en/get-started/example/query-demo-server.rest @@ -12,7 +12,6 @@ POST /rest/evita/Brand/list "entityFetch": { "attributeContentAll": true, "associatedDataContentAll": true, - "priceContentAll": true, "referenceContentAllWithAttributes": {}, "dataInLocales": [] } diff --git a/documentation/user/en/get-started/example/read-entity-by-pk.rest b/documentation/user/en/get-started/example/read-entity-by-pk.rest index 26bfb5173..a0bf9ca90 100644 --- a/documentation/user/en/get-started/example/read-entity-by-pk.rest +++ b/documentation/user/en/get-started/example/read-entity-by-pk.rest @@ -1 +1 @@ -GET /rest/test-catalog/brand/get/1?attributeContent=name \ No newline at end of file +GET /rest/evita/Brand/get/1?attributeContent=name \ No newline at end of file diff --git a/documentation/user/en/get-started/example/update-entity.rest b/documentation/user/en/get-started/example/update-entity.rest index b5c18b00d..e36cb2aec 100644 --- a/documentation/user/en/get-started/example/update-entity.rest +++ b/documentation/user/en/get-started/example/update-entity.rest @@ -1,4 +1,4 @@ -PUT /rest/test-catalog/product/1 +PUT /rest/evita/Product/1 { "entityExistence": "MUST_EXIST", diff --git a/documentation/user/en/use/api/example/create-new-entity.rest b/documentation/user/en/use/api/example/create-new-entity.rest index ac56e96d5..1d7a7844d 100644 --- a/documentation/user/en/use/api/example/create-new-entity.rest +++ b/documentation/user/en/use/api/example/create-new-entity.rest @@ -1,4 +1,4 @@ -PUT /rest/test-catalog/product +PUT /rest/evita/Product { "entityExistence": "MUST_NOT_EXIST", @@ -19,7 +19,7 @@ PUT /rest/test-catalog/product }, { "upsertAttributeMutation": { - "name": "catalogName", + "name": "catalogCode", "value": "X1605EA-MB044W" } }, diff --git a/documentation/user/en/use/api/example/delete-entities-by-query.rest b/documentation/user/en/use/api/example/delete-entities-by-query.rest index 52e6b720c..a808d241b 100644 --- a/documentation/user/en/use/api/example/delete-entities-by-query.rest +++ b/documentation/user/en/use/api/example/delete-entities-by-query.rest @@ -1,4 +1,4 @@ -DELETE /rest/test-catalog/brand +DELETE /rest/evita/brand { "filterBy": { diff --git a/documentation/user/en/use/api/example/evita-query-example.rest b/documentation/user/en/use/api/example/evita-query-example.rest index 6ecbf6c1f..3534bc0a1 100644 --- a/documentation/user/en/use/api/example/evita-query-example.rest +++ b/documentation/user/en/use/api/example/evita-query-example.rest @@ -11,14 +11,14 @@ POST /rest/evita/Product/query "priceValidInNow": true, "priceInCurrency": "CZK", "priceInPriceLists": ["vip", "loyal-customer", "regular-prices"], - "userFilter": { - "facetParameterValuesHaving": { + "userFilter": [{ + "facetParameterValuesHaving": [{ "entityHaving": { "attributeCodeInSet": ["gluten-free", "original-recipe"] } - }, + }], "priceBetween": ["600", "1600"] - } + }] }, "require": { "page": { diff --git a/documentation/user/en/use/api/example/finalization-of-warmup-mode.rest b/documentation/user/en/use/api/example/finalization-of-warmup-mode.rest index 99000daee..74030e031 100644 --- a/documentation/user/en/use/api/example/finalization-of-warmup-mode.rest +++ b/documentation/user/en/use/api/example/finalization-of-warmup-mode.rest @@ -1,4 +1,4 @@ -PATCH /rest/system/catalogs/test-catalog +PATCH /rest/system/catalogs/evita { "catalogState": "ALIVE" diff --git a/documentation/user/en/use/api/example/imperative-catalog-schema-definition.rest b/documentation/user/en/use/api/example/imperative-catalog-schema-definition.rest index 75cddb952..8fb2aa4cf 100644 --- a/documentation/user/en/use/api/example/imperative-catalog-schema-definition.rest +++ b/documentation/user/en/use/api/example/imperative-catalog-schema-definition.rest @@ -1,4 +1,4 @@ -PUT /rest/test-catalog/schema +PUT /rest/evita/schema { "mutations": [ @@ -56,14 +56,14 @@ PUT /rest/test-catalog/schema "createAttributeSchemaMutation": { "name": "code", "type": "String", - "unique": true + "uniquenessType": "UNIQUE_WITHIN_COLLECTION" } }, { "createAttributeSchemaMutation": { "name": "url", "type": "String", - "unique": true, + "uniquenessType": "UNIQUE_WITHIN_COLLECTION", "localized": true } }, diff --git a/documentation/user/en/use/api/example/imperative-collection-schema-definition.rest b/documentation/user/en/use/api/example/imperative-collection-schema-definition.rest index 413617f91..c9f14b92f 100644 --- a/documentation/user/en/use/api/example/imperative-collection-schema-definition.rest +++ b/documentation/user/en/use/api/example/imperative-collection-schema-definition.rest @@ -1,4 +1,4 @@ -PUT /rest/test-catalog/product/schema +PUT /rest/evita/Product/schema { "mutations": [ diff --git a/documentation/user/en/use/api/example/rest-full-query-example.rest b/documentation/user/en/use/api/example/rest-full-query-example.rest index 8b63f7a21..449f5d804 100644 --- a/documentation/user/en/use/api/example/rest-full-query-example.rest +++ b/documentation/user/en/use/api/example/rest-full-query-example.rest @@ -11,21 +11,21 @@ POST /rest/evita/Product/query "priceValidInNow": true, "priceInCurrency": "CZK", "priceInPriceLists": ["vip", "loyal-customer", "regular-prices"], - "userFilter": { - "facetParameterValuesHaving": { + "userFilter": [{ + "facetParameterValuesHaving": [{ "entityHaving": { "attributeCodeInSet": ["gluten-free", "original-recipe"] } - }, + }], "priceBetween": ["600", "1600"] - } + }] }, "orderBy": [{ "attributeCodeNatural": "ASC" }], "require": { "entityFetch": { - "attributeContent": "code", + "attributeContent": ["code"], "referenceCategoriesContent": {} }, "facetSummary": {}, diff --git a/documentation/user/en/use/api/example/rest-get-query-example.rest b/documentation/user/en/use/api/example/rest-get-query-example.rest index a0feaa2c9..8845d60a5 100644 --- a/documentation/user/en/use/api/example/rest-get-query-example.rest +++ b/documentation/user/en/use/api/example/rest-get-query-example.rest @@ -1 +1 @@ -GET /rest/evita/product/1?attributeContent=code&referenceContent=true \ No newline at end of file +GET /rest/evita/Product/get/1?attributeContent=code&referenceContentAll=true \ No newline at end of file diff --git a/documentation/user/en/use/api/example/rest-list-query-example.rest b/documentation/user/en/use/api/example/rest-list-query-example.rest index fee11e86d..2960f93cd 100644 --- a/documentation/user/en/use/api/example/rest-list-query-example.rest +++ b/documentation/user/en/use/api/example/rest-list-query-example.rest @@ -11,14 +11,14 @@ POST /rest/evita/Product/list "priceValidInNow": true, "priceInCurrency": "CZK", "priceInPriceLists": ["vip", "loyal-customer", "regular-prices"], - "userFilter": { - "facetParameterValuesHaving": { + "userFilter": [{ + "facetParameterValuesHaving": [{ "entityHaving": { "attributeCodeInSet": ["gluten-free", "original-recipe"] } - }, + }], "priceBetween": ["600", "1600"] - } + }] }, "orderBy": [ { @@ -27,7 +27,7 @@ POST /rest/evita/Product/list ], "require": { "entityFetch": { - "attributeContent": "code", + "attributeContent": ["code"], "referenceCategoriesContent": {} } } diff --git a/documentation/user/en/use/api/example/update-existing-entity.rest b/documentation/user/en/use/api/example/update-existing-entity.rest index de3ff7b49..3aee31f09 100644 --- a/documentation/user/en/use/api/example/update-existing-entity.rest +++ b/documentation/user/en/use/api/example/update-existing-entity.rest @@ -1,4 +1,4 @@ -PUT /rest/test-catalog/product +PUT /rest/evita/Product { "primaryKey": 1, diff --git a/documentation/user/en/use/api/schema-api.md b/documentation/user/en/use/api/schema-api.md index 27209dc3e..1abf2291d 100644 --- a/documentation/user/en/use/api/schema-api.md +++ b/documentation/user/en/use/api/schema-api.md @@ -147,7 +147,7 @@ the schema definition. You can define a new catalog schema or update an existing one using the [catalog schema API](/documentation/user/en/use/connectors/graphql.md#graphql-api-instances) -at the `https://your-server:5555/gql/test-catalog/schema` URL: +at the `https://your-server:5555/gql/evita/schema` URL: @@ -186,14 +186,14 @@ the schema definition. You can define a new catalog schema or update an existing one using the [catalog API](/documentation/user/en/use/connectors/rest.md#rest-api-instances) -at the `https://your-server:5555/rest/test-catalog/schema` URL: +at the `https://your-server:5555/rest/evita/schema` URL: [Imperative catalog schema definition via REST API](/documentation/user/en/use/api/example/imperative-catalog-schema-definition.rest) -or update the schema of a specific entity collection at e.g. an `https://your-server:5555/rest/test-catalog/product/schema` URL +or update the schema of a specific entity collection at e.g. an `https://your-server:5555/rest/evita/product/schema` URL for the collection `Product` using a REST mutation of the selected collection like this: diff --git a/documentation/user/en/use/api/write-data.md b/documentation/user/en/use/api/write-data.md index fe2e463d9..03e8fda83 100644 --- a/documentation/user/en/use/api/write-data.md +++ b/documentation/user/en/use/api/write-data.md @@ -723,7 +723,7 @@ will create a library with e.g. entity builders that will generate the collectio You can create a new entity or update an existing one using the [catalog data API](/documentation/user/en/use/connectors/graphql.md#graphql-api-instances) -at the `https://your-server:5555/gql/test-catalog` URL. This API contains `upsertCollectionName` GraphQL mutations for each +at the `https://your-server:5555/gql/evita` URL. This API contains `upsertCollectionName` GraphQL mutations for each [entity collection](/documentation/user/en/use/data-model.md#collection) that are customized to collections' [schemas](/documentation/user/en/use/schema.md#entity). These mutations take a collection of evitaDB mutations which define the changes to be applied to an entity. In one go, you can then retrieve the entity with the changes applied by defining @@ -733,7 +733,7 @@ return data. You can create a new entity or update an existing one using the [catalog API](/documentation/user/en/use/connectors/rest.md#rest-api-instances) -at a collection endpoint, for example `https://your-server:5555/test/test-catalog/product` with `PUT` HTTP method. +at a collection endpoint, for example `https://your-server:5555/test/evita/product` with `PUT` HTTP method. There endpoints are customized to collections' [schemas](/documentation/user/en/use/schema.md#entity). These endpoints take a collection of evitaDB mutations which define the changes to be applied to an entity. In one go, you can then retrieve the entity with the changes applied by defining requirements. 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 6f16c01dc..5831c47d9 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 @@ -408,7 +408,7 @@ Stream testDocumentation() throws IOException { new ExampleFilter[]{ // ExampleFilter.CSHARP, ExampleFilter.JAVA, - // ExampleFilter.REST, + ExampleFilter.REST, ExampleFilter.GRAPHQL, ExampleFilter.EVITAQL } diff --git a/evita_functional_tests/src/test/java/io/evitadb/documentation/rest/RestExecutable.java b/evita_functional_tests/src/test/java/io/evitadb/documentation/rest/RestExecutable.java index 4342afa6a..af2da63c1 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/documentation/rest/RestExecutable.java +++ b/evita_functional_tests/src/test/java/io/evitadb/documentation/rest/RestExecutable.java @@ -25,6 +25,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import io.evitadb.documentation.JsonExecutable; import io.evitadb.documentation.UserDocumentationTest.CreateSnippets; @@ -51,6 +52,7 @@ import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; import static io.evitadb.documentation.UserDocumentationTest.readFile; import static io.evitadb.documentation.UserDocumentationTest.resolveSiblingWithDifferentExtension; @@ -73,7 +75,9 @@ public class RestExecutable extends JsonExecutable implements Executable, EvitaT /** * Regex pattern to parse input request into URL and query (request body) */ - private static final Pattern REQUEST_PATTERN = Pattern.compile("([A-Z]+)\\s((/[\\w\\-]+)+)(\\s+([.\\s\\S]+))?"); + private static final Pattern REQUEST_PATTERN = Pattern.compile("([A-Z]+)\\s((/[\\w?=&\\-]+)+)(\\s+([.\\s\\S]+))?"); + private static final Set METHODS_WITH_RESULT = Set.of("POST", "PUT", "GET"); + private static final Pattern REQUEST_DELIMITER_PATTERN = Pattern.compile("---"); /** * Provides access to the {@link RestTestContext} instance. @@ -216,25 +220,48 @@ private static JsonNode extractValueFrom(@Nonnull JsonNode theObject, @Nonnull S @Override public void execute() throws Throwable { - final Matcher request = REQUEST_PATTERN.matcher(sourceContent); - Assert.isPremiseValid(request.matches(), "Invalid request format."); - final String method = request.group(1); - final String path = request.group(2); - final String theQuery = request.group(5); - final boolean shouldHaveResult = Set.of("POST", "PUT", "GET").contains(method); - final RestClient restClient = testContextAccessor.get().getRestClient(); - final JsonNode theResult; - try { - theResult = restClient.call(method, path, theQuery).orElseThrow(); - } catch (Exception ex) { - fail("The query " + theQuery + " failed: " + ex.getMessage(), ex); - return; + + final String[] requests = REQUEST_DELIMITER_PATTERN.split(sourceContent); + boolean shouldHaveResult = false; + final ArrayNode combinedResult = OBJECT_MAPPER.createArrayNode(); + for (String request : requests) { + if (request.isBlank()) { + continue; + } + + final Matcher requestMatcher = REQUEST_PATTERN.matcher(request.strip()); + Assert.isPremiseValid(requestMatcher.matches(), "Invalid request format."); + final String method = requestMatcher.group(1); + final String path = requestMatcher.group(2); + final String theQuery = requestMatcher.group(5); + + if (METHODS_WITH_RESULT.contains(method)) { + shouldHaveResult = true; + } + + final String normalizedQuery = theQuery == null || theQuery.isBlank() + ? "" + : Arrays.stream(theQuery.split("\n")) + .filter(it -> !it.contains("//")) + .collect(Collectors.joining("\n")); + + final JsonNode theResult; + try { + theResult = restClient.call(method, path, normalizedQuery).orElseThrow(); + } catch (Exception ex) { + fail("The query " + normalizedQuery + " failed: " + ex.getMessage(), ex); + return; + } + combinedResult.add(theResult); } + final boolean shouldHaveResultFinal = shouldHaveResult; + final JsonNode theResult = combinedResult.size() == 1 ? combinedResult.get(0) : combinedResult; + if (resource != null) { final List markdownSnippets = outputSnippet.stream() - .map(snippet -> generateMarkdownSnippet(shouldHaveResult, theResult, snippet)) + .map(snippet -> generateMarkdownSnippet(shouldHaveResultFinal, theResult, snippet)) .toList(); for (int i = 0; i < outputSnippet.size(); i++) { From 9d62b0a2c5bfe9726832501adbe9b1a8f9a7b7e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Wed, 13 Dec 2023 12:53:36 +0100 Subject: [PATCH 38/52] docs(#200): example fixes, rewrite of HttpUrlConnection to HttpClient due to the lack of PATCH method support --- .../example/create-small-dataset.rest | 2 +- .../example/delete-entity-by-pk.cs | 2 +- .../example/delete-entity-by-pk.java | 25 +++- .../example/delete-entity-by-pk.rest | 2 +- .../en/query/examples/complexGrammar.rest | 4 +- .../en/use/api/example/create-new-entity.rest | 6 +- .../example/graphql-get-query-example.graphql | 2 +- .../api/example/rest-full-query-example.rest | 4 +- .../api/example/rest-get-query-example.rest | 2 +- .../api/example/update-existing-entity.rest | 7 +- documentation/user/en/use/api/query-data.md | 116 +++++++++--------- ...stDeleteEntityMutationsFunctionalTest.java | 31 +++++ .../io/evitadb/test/client/ApiClient.java | 100 ++++++++------- .../io/evitadb/test/client/GraphQLClient.java | 54 ++++---- .../io/evitadb/test/client/RestClient.java | 55 +++++---- .../src/main/java/module-info.java | 1 + 16 files changed, 237 insertions(+), 176 deletions(-) diff --git a/documentation/user/en/get-started/example/create-small-dataset.rest b/documentation/user/en/get-started/example/create-small-dataset.rest index 9f65a4989..c56660860 100644 --- a/documentation/user/en/get-started/example/create-small-dataset.rest +++ b/documentation/user/en/get-started/example/create-small-dataset.rest @@ -30,7 +30,7 @@ PUT /rest/evita/Brand/3 --- -PUT /rest/evita/Bran/4 +PUT /rest/evita/Brand/4 { "entityExistence": "MUST_NOT_EXIST", diff --git a/documentation/user/en/get-started/example/delete-entity-by-pk.cs b/documentation/user/en/get-started/example/delete-entity-by-pk.cs index 104d02e09..dcd66f504 100644 --- a/documentation/user/en/get-started/example/delete-entity-by-pk.cs +++ b/documentation/user/en/get-started/example/delete-entity-by-pk.cs @@ -2,7 +2,7 @@ "evita", session => { return session.DeleteEntity( "Brand", - 1 + 2 ); } ); \ No newline at end of file diff --git a/documentation/user/en/get-started/example/delete-entity-by-pk.java b/documentation/user/en/get-started/example/delete-entity-by-pk.java index 7e5f06c73..cbea52818 100644 --- a/documentation/user/en/get-started/example/delete-entity-by-pk.java +++ b/documentation/user/en/get-started/example/delete-entity-by-pk.java @@ -1,8 +1,31 @@ +/* + * + * _ _ ____ ____ + * _____ _(_) |_ __ _| _ \| __ ) + * / _ \ \ / / | __/ _` | | | | _ \ + * | __/\ V /| | || (_| | |_| | |_) | + * \___| \_/ |_|\__\__,_|____/|____/ + * + * Copyright (c) 2023 + * + * 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/main/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. + */ + evita.updateCatalog( "evita", session -> { return session.deleteEntity( "Brand", - 1 + 2 ); } ); \ No newline at end of file diff --git a/documentation/user/en/get-started/example/delete-entity-by-pk.rest b/documentation/user/en/get-started/example/delete-entity-by-pk.rest index c757b9264..f386e01a1 100644 --- a/documentation/user/en/get-started/example/delete-entity-by-pk.rest +++ b/documentation/user/en/get-started/example/delete-entity-by-pk.rest @@ -1 +1 @@ -DELETE /rest/evita/Brand/delete/1 \ No newline at end of file +DELETE /rest/evita/Brand/2 \ No newline at end of file diff --git a/documentation/user/en/query/examples/complexGrammar.rest b/documentation/user/en/query/examples/complexGrammar.rest index 08e278ad8..857918785 100644 --- a/documentation/user/en/query/examples/complexGrammar.rest +++ b/documentation/user/en/query/examples/complexGrammar.rest @@ -3,11 +3,11 @@ POST /rest/evita/Product/query { "filterBy": { "entityPrimaryKeyInSet": [1, 2, 3], - "attributeVisibilityEquals": "VISIBLE" + "attributeStatusEquals": "VISIBLE" }, "orderBy": [ { "attributeCodeNatural": "ASC" }, - { "attributePriorityNatural": "DESC" } + { "attributeCatalogNumberNatural": "DESC" } ], "require": { "entityFetch": { diff --git a/documentation/user/en/use/api/example/create-new-entity.rest b/documentation/user/en/use/api/example/create-new-entity.rest index 1d7a7844d..10dc00cb8 100644 --- a/documentation/user/en/use/api/example/create-new-entity.rest +++ b/documentation/user/en/use/api/example/create-new-entity.rest @@ -1,4 +1,4 @@ -PUT /rest/evita/Product +POST /rest/evita/Product { "entityExistence": "MUST_NOT_EXIST", @@ -79,8 +79,8 @@ PUT /rest/evita/Product ], "require": { "entityFetch": { - "attributeContent": [], - "dataInLocales": [] + "attributeContentAll": true, + "dataInLocalesAll": true } } } \ No newline at end of file diff --git a/documentation/user/en/use/api/example/graphql-get-query-example.graphql b/documentation/user/en/use/api/example/graphql-get-query-example.graphql index 13fbe8d35..331bf4ef2 100644 --- a/documentation/user/en/use/api/example/graphql-get-query-example.graphql +++ b/documentation/user/en/use/api/example/graphql-get-query-example.graphql @@ -1,5 +1,5 @@ { - getProduct(primaryKey: 1) { + getProduct(primaryKey: 63049) { primaryKey attributes { code diff --git a/documentation/user/en/use/api/example/rest-full-query-example.rest b/documentation/user/en/use/api/example/rest-full-query-example.rest index 449f5d804..6cd0ded36 100644 --- a/documentation/user/en/use/api/example/rest-full-query-example.rest +++ b/documentation/user/en/use/api/example/rest-full-query-example.rest @@ -28,7 +28,9 @@ POST /rest/evita/Product/query "attributeContent": ["code"], "referenceCategoriesContent": {} }, - "facetSummary": {}, + "facetSummary": { + "statisticsDepth": "COUNTS" + }, "priceHistogram": 30 } } \ No newline at end of file diff --git a/documentation/user/en/use/api/example/rest-get-query-example.rest b/documentation/user/en/use/api/example/rest-get-query-example.rest index 8845d60a5..f75a30b61 100644 --- a/documentation/user/en/use/api/example/rest-get-query-example.rest +++ b/documentation/user/en/use/api/example/rest-get-query-example.rest @@ -1 +1 @@ -GET /rest/evita/Product/get/1?attributeContent=code&referenceContentAll=true \ No newline at end of file +GET /rest/evita/Product/get/63049?attributeContent=code&referenceContentAll=true \ No newline at end of file diff --git a/documentation/user/en/use/api/example/update-existing-entity.rest b/documentation/user/en/use/api/example/update-existing-entity.rest index 3aee31f09..e9f9547e6 100644 --- a/documentation/user/en/use/api/example/update-existing-entity.rest +++ b/documentation/user/en/use/api/example/update-existing-entity.rest @@ -1,7 +1,6 @@ -PUT /rest/evita/Product +PUT /rest/evita/Product/1 { - "primaryKey": 1, "entityExistence": "MUST_EXIST", "mutations": [ { @@ -21,8 +20,8 @@ PUT /rest/evita/Product ], "require": { "entityFetch": { - "attributeContent": [], - "dataInLocales": [] + "attributeContentAll": true, + "dataInLocalesAll": true } } } \ No newline at end of file diff --git a/documentation/user/en/use/api/query-data.md b/documentation/user/en/use/api/query-data.md index 7450ad9f0..39efe7d4c 100644 --- a/documentation/user/en/use/api/query-data.md +++ b/documentation/user/en/use/api/query-data.md @@ -221,17 +221,17 @@ will be fully fetched again. However, we plan to optimize this scenario in the f ### Custom contracts -Data retrieved from evitaDB is represented by the internal evitaDB data structures, which use the domain names -associated with the evitaDB representation. You may want to use your own domain names and data structures in your -application. Fortunately, evitaDB allows you to define your own contracts for data retrieval and use them to fetch and -[write data](write-data.md#custom-contracts) to evitaDB. This chapter describes how to define custom contracts for data +Data retrieved from evitaDB is represented by the internal evitaDB data structures, which use the domain names +associated with the evitaDB representation. You may want to use your own domain names and data structures in your +application. Fortunately, evitaDB allows you to define your own contracts for data retrieval and use them to fetch and +[write data](write-data.md#custom-contracts) to evitaDB. This chapter describes how to define custom contracts for data retrieval. Basic requirements and usage patterns are described in the [Java Connector chapter](../connectors/java.md#custom-contracts). -The read contract is expected to be used for both [schema definition](schema-api.md#declarative-schema-definition) and +The read contract is expected to be used for both [schema definition](schema-api.md#declarative-schema-definition) and data retrieval. However, you can have multiple read contracts with different scopes representing the exact same entity. -In addition to [schema controlling annotations](schema-api.md#schema-controlling-annotations), which you can use to -describe your read contract, you can also use shortcut annotations, which don't require you to repeat all of the entity +In addition to [schema controlling annotations](schema-api.md#schema-controlling-annotations), which you can use to +describe your read contract, you can also use shortcut annotations, which don't require you to repeat all of the entity structure details required for the schema definition:

@@ -250,20 +250,20 @@ structure details required for the schema definition:
evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/AssociatedDataRef.java
- Annotation can be placed on methods that should return entity [associated data](#associated-data). + Annotation can be placed on methods that should return entity [associated data](#associated-data). If the associated data represents a custom Java type converted into [complex data type](../data-types.md#complex-data-types), the implementation provides automatic conversion for that data type.
evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/PriceForSaleRef.java
- Annotation can be placed on method returning - evita_api/src/main/java/io/evitadb/api/requestResponse/data/PriceContract.java type + Annotation can be placed on method returning + evita_api/src/main/java/io/evitadb/api/requestResponse/data/PriceContract.java type to provide access to price for sale of the entity.
evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/ReferenceRef.java
- Annotation can be placed on methods that should return entity [reference](#reference) to another entity. - It can point to another model class (interface/class/record) that contains properties for `@ReferencedEntity` + Annotation can be placed on methods that should return entity [reference](#reference) to another entity. + It can point to another model class (interface/class/record) that contains properties for `@ReferencedEntity` and `@ReferencedEntityGroup` annotations and relation attributes or directly to different entity read contract annotated with `@Entity` or `@EntityRef` annotation.
@@ -271,27 +271,27 @@ structure details required for the schema definition: -Because evitaDB allows partial fetches of the entity, not all data in the contract may be available. If you access it, -you will get a NULL value, which can represent both the fact that the data is not available and the fact that the data +Because evitaDB allows partial fetches of the entity, not all data in the contract may be available. If you access it, +you will get a NULL value, which can represent both the fact that the data is not available and the fact that the data does not exist. If you need to distinguish between these two cases, your methods should use the -evita_api/src/main/java/io/evitadb/api/exception/ContextMissingException.java. -This exception is a runtime exception, so the caller is not forced to handle it, but it signals the automatic evitaDB +evita_api/src/main/java/io/evitadb/api/exception/ContextMissingException.java. +This exception is a runtime exception, so the caller is not forced to handle it, but it signals the automatic evitaDB implementation to throw an exception if the requested data was not fetched with the entity. -Lazy loading of data is not yet supported for custom contracts, but we plan to automatically fetch missing data if +Lazy loading of data is not yet supported for custom contracts, but we plan to automatically fetch missing data if the method call occurs in a scope where the evita session is available. All the examples in this chapter come in three variants: interface, record, and class. The interface variant is the most -versatile and can also be applied to classes. However, if you follow the record or class examples with final fields, +versatile and can also be applied to classes. However, if you follow the record or class examples with final fields, where the data is passed through the constructor, you're limited in some features and other behavior: - You cannot distinguish between the fact that the data was not fetched and the fact that the data does not exist. -- You cannot use controllable accessors that change output based on method parameters (for example, `String getName(Locale locale)`), +- You cannot use controllable accessors that change output based on method parameters (for example, `String getName(Locale locale)`), because there is no way to represent the method parameters in the constructor arguments. -The record and immutable class example variants are suitable for secondary data structures or simplified structures +The record and immutable class example variants are suitable for secondary data structures or simplified structures returned as a result of reference getter calls, where you don't need the full-fledged read contract. Methods annotated with these annotations must follow the expected method signature conventions: @@ -310,15 +310,15 @@ or evita_api/src/main/java/io/evitadb/api/requestResponse/data/anno #### Attributes -To access the entity or reference attribute, you must use the appropriate data type and annotate it with the +To access the entity or reference attribute, you must use the appropriate data type and annotate it with the evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/Attribute.java -or evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/AttributeRef.java -annotation. The data type can be wrapped in [Optional](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Optional.html) -(or its counterparts [OptionalInt](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/OptionalInt.html) +or evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/AttributeRef.java +annotation. The data type can be wrapped in [Optional](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Optional.html) +(or its counterparts [OptionalInt](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/OptionalInt.html) or [OptionalLong](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/OptionalLong.html)). If the attribute represents a multi-value type (array), you can also wrap it in [Collection](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Collection.html) -(or its specializations [List](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/List.html) +(or its specializations [List](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/List.html) or [Set](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Set.html)). The rules apply to both for entity and reference attributes: @@ -329,7 +329,7 @@ or [Set](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/ -Java enum data types are automatically converted to evitaDB string data type using the `name()` method and vice versa +Java enum data types are automatically converted to evitaDB string data type using the `name()` method and vice versa using the `valueOf()` method. @@ -337,14 +337,14 @@ using the `valueOf()` method. Avoid declaring methods that return a primitive data type without throwing the `ContextMissingException`. The method -call may fail with a `NullPointerException` if the data wasn't fetched even though it was declared as mandatory +call may fail with a `NullPointerException` if the data wasn't fetched even though it was declared as mandatory (not nullable). #### Associated data -To access the entity or reference associated data, you must use the appropriate data type and annotate it with +To access the entity or reference associated data, you must use the appropriate data type and annotate it with the evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/AssociatedData.java or evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/AssociatedDataRef.java annotation. The data type can be wrapped in [Optional](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Optional.html) @@ -366,7 +366,7 @@ from ["complex data type"](../data-types.md#complex-data-types) using [documente #### Prices -To access the entity prices, you must always work with +To access the entity prices, you must always work with evita_api/src/main/java/io/evitadb/api/requestResponse/data/PriceContract.java data type and annotate the methods with the evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/Price.java, evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/PriceForSale.java or @@ -390,13 +390,13 @@ because the method call may fail with a `NullPointerException` in such a case. #### Hierarchy -To access the hierarchy placement information of the entity (i.e., its parent), you must use either the numeric data -type, your own custom interface type, evita_api/src/main/java/io/evitadb/api/requestResponse/data/SealedEntity.java -or evita_api/src/main/java/io/evitadb/api/requestResponse/data/structure/EntityReference.java +To access the hierarchy placement information of the entity (i.e., its parent), you must use either the numeric data +type, your own custom interface type, evita_api/src/main/java/io/evitadb/api/requestResponse/data/SealedEntity.java +or evita_api/src/main/java/io/evitadb/api/requestResponse/data/structure/EntityReference.java data type and annotate it with the evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/ParentEntity.java -annotation. The datatype can be wrapped in -[Optional](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Optional.html) -(or its counterparts [OptionalInt](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/OptionalInt.html) +annotation. The datatype can be wrapped in +[Optional](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Optional.html) +(or its counterparts [OptionalInt](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/OptionalInt.html) or [OptionalLong](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/OptionalLong.html)). @@ -405,12 +405,12 @@ or [OptionalLong](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/j -The method may return null if the entity is a root entity. Therefore, it's not recommended to use primitive data types, +The method may return null if the entity is a root entity. Therefore, it's not recommended to use primitive data types, because the method call may fail with a `NullPointerException` in such a case. #### References -To access the references of the entity, you must use either the numeric data type, your own custom interface type, +To access the references of the entity, you must use either the numeric data type, your own custom interface type, evita_api/src/main/java/io/evitadb/api/requestResponse/data/EntityReferenceContract.java or evita_api/src/main/java/io/evitadb/api/requestResponse/data/ReferenceContract.java data type and annotate it with the evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/Reference.java @@ -430,15 +430,15 @@ If the method can return multiple references, you need to wrap it in [Collection -When you declare to return a custom interface, you can return either the custom interface of the referenced entity -(i.e., an interface annotated with evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/Entity.java -or evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/EntityRef.java) -or interface mapping the reference. The latter allows you to access attributes of the reference and may also contain +When you declare to return a custom interface, you can return either the custom interface of the referenced entity +(i.e., an interface annotated with evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/Entity.java +or evita_api/src/main/java/io/evitadb/api/requestResponse/data/annotation/EntityRef.java) +or interface mapping the reference. The latter allows you to access attributes of the reference and may also contain additional methods to access the referenced entity. In the former case, you can access the referenced entity, but not the reference attributes. -Methods annotated with this annotation should respect the cardinality of the reference. If the cardinality is -`EXACTLY_ONE` or `ZERO_OR_ONE`, the method should directly return the entity or the reference to it. If the cardinality +Methods annotated with this annotation should respect the cardinality of the reference. If the cardinality is +`EXACTLY_ONE` or `ZERO_OR_ONE`, the method should directly return the entity or the reference to it. If the cardinality is `ZERO_OR_MORE` or `ONE_OR_MORE`, the method should return a collection or array of entities or references to them. #### Access to evitaDB data structures @@ -456,20 +456,20 @@ Your read contract can implement the following interfaces to access the underlyi
evita_api/src/main/java/io/evitadb/api/proxy/WithEntityContract.java
which provides access to the underlying evitaDB entity via the `entity()` method
evita_api/src/main/java/io/evitadb/api/proxy/WithEntityBuilder.java
-
which provides access to the underlying evitaDB entity builder via the `entityBuilder()` method (automatically +
which provides access to the underlying evitaDB entity builder via the `entityBuilder()` method (automatically creates a new one if it hasn't been requested yet) or the `entityBuilderIfPresent` method.
-All generated proxies automatically implement the evita_api/src/main/java/io/evitadb/api/proxy/EvitaProxy.java +All generated proxies automatically implement the evita_api/src/main/java/io/evitadb/api/proxy/EvitaProxy.java interface, so you can distinguish them from other classes. -evitaDB cannot provide an automatic implementation for these interfaces if your read contract is a record or a +evitaDB cannot provide an automatic implementation for these interfaces if your read contract is a record or a final/sealed class. In such cases you have to implement these interfaces manually. @@ -501,8 +501,8 @@ When this class is imported statically, the C# query definition looks like the s Thanks to type inference, the IDE will help you with auto-completion of the constraints that make sense in a particular context. -This is an example of how the query is composed and how evitaDB is called. -The example imports previously mentioned interface +This is an example of how the query is composed and how evitaDB is called. +The example imports previously mentioned interface EvitaDB.Client/Queries/IQueryConstraints.cs statically. @@ -604,8 +604,8 @@ developers, and it's much more optimal to just enrich the existing entity (using only missing data) instead of fetching the entire entity again. -Lazy Fetching is currently only fully implemented for embedded evitaDB. If you are using evitaDB remotely via -EvitaDB.Client/EvitaClient.cs you can still use the `EnrichEntity` method on the +Lazy Fetching is currently only fully implemented for embedded evitaDB. If you are using evitaDB remotely via +EvitaDB.Client/EvitaClient.cs you can still use the `EnrichEntity` method on the EvitaDB.Client/EvitaClientSession.cs instance, but the entity will be fully fetched again. However, we plan to optimize this scenario in the future. @@ -618,8 +618,8 @@ defined memory limit. Details about caching are [described here](../../deep-dive the implementation of an own cache on top of the evitaDB cache is not recommended. If you are using EvitaDB.Client/EvitaClient.cs, implementing the local cache may save you network costs and -give you better latency. The problem is related to cache invalidation. You'd have to query only the entity references -that contain version information and fetch the entities that are not in the cache with a separate request. +give you better latency. The problem is related to cache invalidation. You'd have to query only the entity references +that contain version information and fetch the entities that are not in the cache with a separate request. So instead of one network request, you have to make two. The benefit of the local cache is therefore somewhat questionable.
@@ -653,7 +653,7 @@ as they provide quick access to entities. -[Java query example](/documentation/user/en/use/api/example/graphql-get-query-example.graphql) +[GraphQL get query example](/documentation/user/en/use/api/example/graphql-get-query-example.graphql) ### `list` queries @@ -666,7 +666,7 @@ requirements are needed. -[Java query example](/documentation/user/en/use/api/example/graphql-list-query-example.graphql) +[GraphQL list query example](/documentation/user/en/use/api/example/graphql-list-query-example.graphql) ### `query` queries @@ -679,7 +679,7 @@ one query. -[Java query example](/documentation/user/en/use/api/example/graphql-full-query-example.graphql) +[GraphQL full query example](/documentation/user/en/use/api/example/graphql-full-query-example.graphql)
@@ -707,7 +707,7 @@ as they provide quick access to entities. -[Java query example](/documentation/user/en/use/api/example/rest-get-query-example.rest) +[REST get query example](/documentation/user/en/use/api/example/rest-get-query-example.rest) ### `list` queries @@ -720,7 +720,7 @@ requirements are needed. -[Java query example](/documentation/user/en/use/api/example/rest-list-query-example.rest) +[REST list query example](/documentation/user/en/use/api/example/rest-list-query-example.rest) ### `query` queries @@ -733,7 +733,7 @@ one query. -[Java query example](/documentation/user/en/use/api/example/rest-full-query-example.rest) +[REST full query example](/documentation/user/en/use/api/example/rest-full-query-example.rest)
\ No newline at end of file diff --git a/evita_functional_tests/src/test/java/io/evitadb/externalApi/rest/api/catalog/dataApi/CatalogRestDeleteEntityMutationsFunctionalTest.java b/evita_functional_tests/src/test/java/io/evitadb/externalApi/rest/api/catalog/dataApi/CatalogRestDeleteEntityMutationsFunctionalTest.java index 4c4f4ce04..cad05354f 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/externalApi/rest/api/catalog/dataApi/CatalogRestDeleteEntityMutationsFunctionalTest.java +++ b/evita_functional_tests/src/test/java/io/evitadb/externalApi/rest/api/catalog/dataApi/CatalogRestDeleteEntityMutationsFunctionalTest.java @@ -67,6 +67,37 @@ protected DataCarrier setUp(Evita evita, EvitaServer evitaServer) { return super.setUpData(evita, evitaServer, 20); } + @Test + @UseDataSet(REST_THOUSAND_PRODUCTS_FOR_DELETE) + @DisplayName("Should delete entity by primary key") + void shouldDeleteEntityByPrimaryKey(Evita evita, RestTester tester) { + final List entitiesToDelete = getEntities( + evita, + query( + collection(Entities.PRODUCT), + filterBy( + attributeLessThan(ATTRIBUTE_QUANTITY, 5500) + ), + require( + strip(0, 1), + entityFetch( + attributeContent(ATTRIBUTE_CODE) + ) + ) + ), + SealedEntity.class + ); + assertEquals(1, entitiesToDelete.size()); + + tester.test(TEST_CATALOG) + .httpMethod(Request.METHOD_DELETE) + .urlPathSuffix("/PRODUCT/" + entitiesToDelete.get(0).getPrimaryKey()) + .executeAndThen() + .statusCode(200); + + assertProductDeleted(entitiesToDelete.get(0).getPrimaryKey(), tester); + } + @Test @UseDataSet(REST_THOUSAND_PRODUCTS_FOR_DELETE) @DisplayName("Should delete entity by query") diff --git a/evita_test_support/src/main/java/io/evitadb/test/client/ApiClient.java b/evita_test_support/src/main/java/io/evitadb/test/client/ApiClient.java index 09fdb48a7..78020565e 100644 --- a/evita_test_support/src/main/java/io/evitadb/test/client/ApiClient.java +++ b/evita_test_support/src/main/java/io/evitadb/test/client/ApiClient.java @@ -28,19 +28,17 @@ import io.evitadb.exception.EvitaInternalError; import javax.annotation.Nonnull; -import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; -import java.io.BufferedReader; +import javax.net.ssl.X509ExtendedTrustManager; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.nio.charset.StandardCharsets; +import java.net.Socket; +import java.net.http.HttpClient; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.CertificateException; import java.security.cert.X509Certificate; /** @@ -51,6 +49,7 @@ abstract class ApiClient { protected static final ObjectMapper objectMapper = new ObjectMapper(); + protected final HttpClient client; @Nonnull protected final String url; @@ -61,53 +60,64 @@ protected ApiClient(@Nonnull String url) { protected ApiClient(@Nonnull String url, boolean validateSsl) { this.url = url; - if (!validateSsl) { + if (validateSsl) { + this.client = HttpClient.newHttpClient(); + } else { // Create a trust manager that does not validate certificate chains - final TrustManager[] trustAllCerts = new TrustManager[] { - new X509TrustManager() { - public X509Certificate[] getAcceptedIssuers() { - return null; - } - public void checkClientTrusted(X509Certificate[] certs, String authType) {} - public void checkServerTrusted(X509Certificate[] certs, String authType) {} - } - }; - // Install the all-trusting trust manager - final SSLContext sc; + final TrustManager trustManager = new NoValidateTrustManager(); + SSLContext sslContext = null; try { - sc = SSLContext.getInstance("SSL"); - } catch (NoSuchAlgorithmException e) { - throw new EvitaInternalError("Cannot get SSL context.", e); + sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, new TrustManager[]{trustManager}, new SecureRandom()); + } catch (NoSuchAlgorithmException | KeyManagementException e) { + throw new EvitaInternalError("Could create no-validate trust manager: ", e); } - try { - sc.init(null, trustAllCerts, new java.security.SecureRandom()); - } catch (KeyManagementException e) { - throw new EvitaInternalError("Cannot init SSL context with custom trust manager.", e); - } - HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); - // Install the all-trusting host verifier - HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true); + this.client = HttpClient.newBuilder() + .sslContext(sslContext) + .build(); } } - protected void writeRequestBody(@Nonnull HttpURLConnection connection, @Nonnull String body) throws IOException { - try (OutputStream os = connection.getOutputStream()) { - byte[] input = body.getBytes(StandardCharsets.UTF_8); - os.write(input, 0, input.length); - } + @Nonnull + protected JsonNode readResponseBody(@Nonnull String body) throws IOException { + return objectMapper.readTree(body); } - @Nonnull - protected JsonNode readResponseBody(@Nonnull InputStream bodyStream) throws IOException { - final StringBuilder rawResponseBody = new StringBuilder(); - try (BufferedReader br = new BufferedReader(new InputStreamReader(bodyStream, StandardCharsets.UTF_8))) { - String responseLine; - while ((responseLine = br.readLine()) != null) { - rawResponseBody.append(responseLine.trim()); - } + private static class NoValidateTrustManager extends X509ExtendedTrustManager { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException { + + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException { + } - return objectMapper.readTree(rawResponseBody.toString()); + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException { + + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) throws CertificateException { + + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[]{}; + } } } diff --git a/evita_test_support/src/main/java/io/evitadb/test/client/GraphQLClient.java b/evita_test_support/src/main/java/io/evitadb/test/client/GraphQLClient.java index 05a01a406..2708f8429 100644 --- a/evita_test_support/src/main/java/io/evitadb/test/client/GraphQLClient.java +++ b/evita_test_support/src/main/java/io/evitadb/test/client/GraphQLClient.java @@ -31,8 +31,10 @@ import javax.annotation.Nonnull; import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URL; +import java.net.URI; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.net.http.HttpResponse.BodyHandlers; /** * Simple client for calling GraphQL requests from documentation. @@ -56,52 +58,38 @@ public JsonNode call(@Nonnull String document) { @Nonnull public JsonNode call(@Nonnull String instancePath, @Nonnull String document) { - HttpURLConnection connection = null; try { - connection = createConnection(instancePath); - writeRequestBody(connection, document); + final HttpRequest request = createRequest(instancePath, document); + final HttpResponse response = client.send(request, BodyHandlers.ofString()); - connection.connect(); - final int responseCode = connection.getResponseCode(); + final int responseCode = response.statusCode(); if (responseCode == 200) { - final JsonNode responseBody = readResponseBody(connection.getInputStream()); + final JsonNode responseBody = readResponseBody(response.body()); validateResponseBody(responseBody); return responseBody; } if (responseCode >= 400 && responseCode <= 499 && responseCode != 404) { - final JsonNode errorResponse = readResponseBody(connection.getErrorStream()); - final String errorResponseString = objectMapper.writeValueAsString(errorResponse); - throw new EvitaInternalError("Call to GraphQL instance `" + this.url + instancePath + "` ended with status " + responseCode + ", query was:\n" + document + "\n and response was: \n" + errorResponseString); + throw new EvitaInternalError("Call to GraphQL instance `" + this.url + instancePath + "` ended with status " + responseCode + ", query was:\n" + document + "\n and response was: \n" + response.body()); } throw new EvitaInternalError("Call to GraphQL server ended with status " + responseCode + ", query was:\n" + document); - } catch (IOException e) { + } catch (IOException | InterruptedException e) { throw new EvitaInternalError("Unexpected error.", e); - } finally { - if (connection != null) { - connection.disconnect(); - } } } @Nonnull - private HttpURLConnection createConnection(@Nonnull String instancePath) throws IOException { - final URL url = new URL(this.url + instancePath); - final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setRequestMethod("POST"); - connection.setRequestProperty("Content-Type", "application/json"); - connection.setRequestProperty("Accept", "application/graphql-response+json"); - connection.setDoOutput(true); - return connection; - } - - @Override - protected void writeRequestBody(@Nonnull HttpURLConnection connection, @Nonnull String document) throws IOException { + private HttpRequest createRequest(@Nonnull String instancePath, @Nonnull String document) throws IOException { final GraphQLRequest requestBody = new GraphQLRequest(document, null, null, null); final String requestBodyJson = objectMapper.writeValueAsString(requestBody); - super.writeRequestBody(connection, requestBodyJson); + return HttpRequest.newBuilder() + .uri(URI.create(this.url + instancePath)) + .method("POST", HttpRequest.BodyPublishers.ofString(requestBodyJson)) + .header("Accept", "application/graphql-response+json") + .header("Content-Type", "application/json") + .build(); } private void validateResponseBody(@Nonnull JsonNode responseBody) throws JsonProcessingException { @@ -113,8 +101,14 @@ private void validateResponseBody(@Nonnull JsonNode responseBody) throws JsonPro final JsonNode data = responseBody.get("data"); Assert.isPremiseValid( - data != null && !data.isNull() && !data.isEmpty(), + data != null && !data.isNull() && (data.isValueNode() || !data.isEmpty()), "Call to GraphQL server ended with empty data." ); + data.elements().forEachRemaining(element -> { + Assert.isPremiseValid( + element != null && !element.isNull(), + "Call to GraphQL server ended with empty data." + ); + }); } } diff --git a/evita_test_support/src/main/java/io/evitadb/test/client/RestClient.java b/evita_test_support/src/main/java/io/evitadb/test/client/RestClient.java index 081e69377..5fb81ba8a 100644 --- a/evita_test_support/src/main/java/io/evitadb/test/client/RestClient.java +++ b/evita_test_support/src/main/java/io/evitadb/test/client/RestClient.java @@ -23,6 +23,7 @@ package io.evitadb.test.client; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import io.evitadb.exception.EvitaInternalError; import io.evitadb.utils.Assert; @@ -30,9 +31,11 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.IOException; -import java.net.HttpURLConnection; +import java.net.URI; import java.net.URISyntaxException; -import java.net.URL; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.net.http.HttpResponse.BodyHandlers; import java.util.Optional; /** @@ -52,46 +55,44 @@ public RestClient(@Nonnull String url, boolean validateSsl) { @Nullable public Optional call(@Nonnull String method, @Nonnull String resource, @Nullable String body) { - HttpURLConnection connection = null; try { - connection = createConnection(method, resource); - if (body != null && !body.isBlank()) { - writeRequestBody(connection, body); - } - - connection.connect(); + final HttpRequest request = createRequest(method, resource, body); + final HttpResponse response = client.send(request, BodyHandlers.ofString()); - final int responseCode = connection.getResponseCode(); + final int responseCode = response.statusCode(); if (responseCode == 200) { - return Optional.of(readResponseBody(connection.getInputStream())); + final JsonNode responseBody = readResponseBody(response.body()); + validateResponseBody(responseBody); + + return Optional.of(responseBody); } if (responseCode == 404) { return Optional.empty(); } if (responseCode >= 400 && responseCode <= 499) { - final JsonNode errorResponse = readResponseBody(connection.getErrorStream()); - final String errorResponseString = objectMapper.writeValueAsString(errorResponse); - throw new EvitaInternalError("Call to REST server `" + this.url + resource + "` ended with status " + responseCode + " and response: \n" + errorResponseString); + throw new EvitaInternalError("Call to REST server `" + this.url + resource + "` ended with status " + responseCode + " and response: \n" + response.body()); } throw new EvitaInternalError("Call to REST server `" + this.url + resource + "` ended with status " + responseCode); - } catch (IOException | URISyntaxException e) { + } catch (IOException | URISyntaxException | InterruptedException e) { throw new EvitaInternalError("Unexpected error.", e); - } finally { - if (connection != null) { - connection.disconnect(); - } } } @Nonnull - private HttpURLConnection createConnection(@Nonnull String method, @Nonnull String resource) throws IOException, URISyntaxException { - final URL url = new URL(this.url + resource); - final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setRequestProperty("Accept", "application/json"); - connection.setRequestProperty("Content-Type", "application/json"); - connection.setRequestMethod(method); - connection.setDoOutput(true); - return connection; + private HttpRequest createRequest(@Nonnull String method, @Nonnull String resource, @Nullable String body) throws IOException, URISyntaxException { + return HttpRequest.newBuilder() + .uri(URI.create(this.url + resource)) + .method(method, body != null && !body.isBlank() ? HttpRequest.BodyPublishers.ofString(body) : HttpRequest.BodyPublishers.noBody()) + .header("Accept", "application/json") + .header("Content-Type", "application/json") + .build(); + } + + private void validateResponseBody(@Nonnull JsonNode responseBody) throws JsonProcessingException { + Assert.isPremiseValid( + responseBody != null && !responseBody.isNull(), + "Call to REST server ended with empty data." + ); } } diff --git a/evita_test_support/src/main/java/module-info.java b/evita_test_support/src/main/java/module-info.java index ae37a6f0e..284bfc67f 100644 --- a/evita_test_support/src/main/java/module-info.java +++ b/evita_test_support/src/main/java/module-info.java @@ -16,6 +16,7 @@ requires org.slf4j; requires ch.qos.logback.core; requires rest.assured; + requires java.net.http; requires evita.api; requires evita.engine; From 03d5f5f7efd7948b0aed34929bc5a8f0fe3a0143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Wed, 13 Dec 2023 14:07:12 +0100 Subject: [PATCH 39/52] docs(#200): example fixes --- .../imperative-catalog-schema-definition.graphql | 12 ++++++++++++ .../imperative-catalog-schema-definition.rest | 11 +++++++++++ .../use/api/example/imperative-schema-definition.cs | 5 +++++ documentation/user/en/use/api/schema-api.md | 4 ++-- 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/documentation/user/en/use/api/example/imperative-catalog-schema-definition.graphql b/documentation/user/en/use/api/example/imperative-catalog-schema-definition.graphql index 736cbf1e0..f5d6c8ca9 100644 --- a/documentation/user/en/use/api/example/imperative-catalog-schema-definition.graphql +++ b/documentation/user/en/use/api/example/imperative-catalog-schema-definition.graphql @@ -1,6 +1,18 @@ mutation { updateCatalogSchema( mutations: [ + # first create stubs of the entity schemas that the product will reference + { + createEntitySchemaMutation: { + entityType: "Brand" + } + }, + { + createEntitySchemaMutation: { + entityType: "Category" + } + }, + { createEntitySchemaMutation: { entityType: "Product" diff --git a/documentation/user/en/use/api/example/imperative-catalog-schema-definition.rest b/documentation/user/en/use/api/example/imperative-catalog-schema-definition.rest index 8fb2aa4cf..6f9d0f971 100644 --- a/documentation/user/en/use/api/example/imperative-catalog-schema-definition.rest +++ b/documentation/user/en/use/api/example/imperative-catalog-schema-definition.rest @@ -2,6 +2,17 @@ PUT /rest/evita/schema { "mutations": [ + // first create stubs of the entity schemas that the product will reference + { + "createEntitySchemaMutation": { + "entityType": "Brand" + } + }, + { + "createEntitySchemaMutation": { + "entityType": "Category" + } + }, { "createEntitySchemaMutation": { "entityType": "Product" diff --git a/documentation/user/en/use/api/example/imperative-schema-definition.cs b/documentation/user/en/use/api/example/imperative-schema-definition.cs index 2ff80aba3..cbeb25e4a 100644 --- a/documentation/user/en/use/api/example/imperative-schema-definition.cs +++ b/documentation/user/en/use/api/example/imperative-schema-definition.cs @@ -1,6 +1,11 @@ evita.UpdateCatalog( "evita", session => { + + /* first create stubs of the entity schemas that the product will reference */ + session.DefineEntitySchema("Brand"); + session.DefineEntitySchema("Category"); + session.DefineEntitySchema("Product") /* all is strictly verified but associated data and references can be added on the fly */ diff --git a/documentation/user/en/use/api/schema-api.md b/documentation/user/en/use/api/schema-api.md index 1abf2291d..920e788fb 100644 --- a/documentation/user/en/use/api/schema-api.md +++ b/documentation/user/en/use/api/schema-api.md @@ -156,7 +156,7 @@ at the `https://your-server:5555/gql/evita/schema` URL: or update the schema of a specific entity collection at the same URL using a GraphQL mutation of the selected collection like this: - + [Imperative collection schema definition via GraphQL API](/documentation/user/en/use/api/example/imperative-collection-schema-definition.graphql) @@ -196,7 +196,7 @@ at the `https://your-server:5555/rest/evita/schema` URL: or update the schema of a specific entity collection at e.g. an `https://your-server:5555/rest/evita/product/schema` URL for the collection `Product` using a REST mutation of the selected collection like this: - + [Imperative collection schema definition via REST API](/documentation/user/en/use/api/example/imperative-collection-schema-definition.rest) From 6c19dea622a71657bdf33d4ca8f1c4436924e85d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Wed, 13 Dec 2023 14:40:19 +0100 Subject: [PATCH 40/52] docs(#200): temporary fix of "HTTP/1.1 header parser received no bytes" when using Java HttpClient instead of HttpUrlConnection to NOT reuse connections --- .../main/java/io/evitadb/test/client/ApiClient.java | 13 ++++++++++--- .../java/io/evitadb/test/client/GraphQLClient.java | 2 +- .../java/io/evitadb/test/client/RestClient.java | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/evita_test_support/src/main/java/io/evitadb/test/client/ApiClient.java b/evita_test_support/src/main/java/io/evitadb/test/client/ApiClient.java index 78020565e..d4f72a6da 100644 --- a/evita_test_support/src/main/java/io/evitadb/test/client/ApiClient.java +++ b/evita_test_support/src/main/java/io/evitadb/test/client/ApiClient.java @@ -49,8 +49,8 @@ abstract class ApiClient { protected static final ObjectMapper objectMapper = new ObjectMapper(); - protected final HttpClient client; + protected final boolean validateSsl; @Nonnull protected final String url; protected ApiClient(@Nonnull String url) { @@ -59,9 +59,16 @@ protected ApiClient(@Nonnull String url) { protected ApiClient(@Nonnull String url, boolean validateSsl) { this.url = url; + this.validateSsl = validateSsl; + } + + @Nonnull + protected HttpClient createClient() { + // todo lho - this is terrible, but I all other way result in `HTTP/1.1 header parser received no bytes` if (validateSsl) { - this.client = HttpClient.newHttpClient(); + return HttpClient.newBuilder() + .build(); } else { // Create a trust manager that does not validate certificate chains final TrustManager trustManager = new NoValidateTrustManager(); @@ -73,7 +80,7 @@ protected ApiClient(@Nonnull String url, boolean validateSsl) { throw new EvitaInternalError("Could create no-validate trust manager: ", e); } - this.client = HttpClient.newBuilder() + return HttpClient.newBuilder() .sslContext(sslContext) .build(); } diff --git a/evita_test_support/src/main/java/io/evitadb/test/client/GraphQLClient.java b/evita_test_support/src/main/java/io/evitadb/test/client/GraphQLClient.java index 2708f8429..fc7a8db23 100644 --- a/evita_test_support/src/main/java/io/evitadb/test/client/GraphQLClient.java +++ b/evita_test_support/src/main/java/io/evitadb/test/client/GraphQLClient.java @@ -60,7 +60,7 @@ public JsonNode call(@Nonnull String document) { public JsonNode call(@Nonnull String instancePath, @Nonnull String document) { try { final HttpRequest request = createRequest(instancePath, document); - final HttpResponse response = client.send(request, BodyHandlers.ofString()); + final HttpResponse response = createClient().send(request, BodyHandlers.ofString()); final int responseCode = response.statusCode(); if (responseCode == 200) { diff --git a/evita_test_support/src/main/java/io/evitadb/test/client/RestClient.java b/evita_test_support/src/main/java/io/evitadb/test/client/RestClient.java index 5fb81ba8a..764594adf 100644 --- a/evita_test_support/src/main/java/io/evitadb/test/client/RestClient.java +++ b/evita_test_support/src/main/java/io/evitadb/test/client/RestClient.java @@ -57,7 +57,7 @@ public RestClient(@Nonnull String url, boolean validateSsl) { public Optional call(@Nonnull String method, @Nonnull String resource, @Nullable String body) { try { final HttpRequest request = createRequest(method, resource, body); - final HttpResponse response = client.send(request, BodyHandlers.ofString()); + final HttpResponse response = createClient().send(request, BodyHandlers.ofString()); final int responseCode = response.statusCode(); if (responseCode == 200) { From 46d7954028a6861719326475ed64a413eb67c3b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Wed, 13 Dec 2023 14:52:45 +0100 Subject: [PATCH 41/52] docs(#200): formating fix --- .../examples/custom-contract-writing.java | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/documentation/user/en/use/connectors/examples/custom-contract-writing.java b/documentation/user/en/use/connectors/examples/custom-contract-writing.java index 552033124..0cf0c3e92 100644 --- a/documentation/user/en/use/connectors/examples/custom-contract-writing.java +++ b/documentation/user/en/use/connectors/examples/custom-contract-writing.java @@ -6,6 +6,29 @@ session.createNewEntity( ProductEditor.class, 100 ) + /* + * + * _ _ ____ ____ + * _____ _(_) |_ __ _| _ \| __ ) + * / _ \ \ / / | __/ _` | | | | _ \ + * | __/\ V /| | || (_| | |_| | |_) | + * \___| \_/ |_|\__\__,_|____/|____/ + * + * Copyright (c) 2023 + * + * 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/main/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. + */ + // fill the data .setCode("JP328a01a") .setName("Creative OUTLIER FREE PRO", Locale.ENGLISH) @@ -33,7 +56,7 @@ whichIs -> whichIs .setMarket("Asia") .setBrandGroup(31) -) + ) // and store the modified product back .upsertVia(session); } From 2ca56b671482fe5f2035b1798c1af7c3eb703511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Novotn=C3=BD?= Date: Wed, 13 Dec 2023 15:24:56 +0100 Subject: [PATCH 42/52] docs: fixed last examples --- .../examples/custom-contract-writing.java | 1 + .../examples/sealed-instance-example.java | 26 ++++++++++++++++++- .../documentation/UserDocumentationTest.java | 2 +- .../evitadb/documentation/mock/Product.java | 26 ++++++++++++++++++- 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/documentation/user/en/use/connectors/examples/custom-contract-writing.java b/documentation/user/en/use/connectors/examples/custom-contract-writing.java index 0cf0c3e92..f117d137e 100644 --- a/documentation/user/en/use/connectors/examples/custom-contract-writing.java +++ b/documentation/user/en/use/connectors/examples/custom-contract-writing.java @@ -6,6 +6,7 @@ session.createNewEntity( ProductEditor.class, 100 ) + /* * * _ _ ____ ____ diff --git a/documentation/user/en/use/connectors/examples/sealed-instance-example.java b/documentation/user/en/use/connectors/examples/sealed-instance-example.java index e0cf5bb15..e051a5acd 100644 --- a/documentation/user/en/use/connectors/examples/sealed-instance-example.java +++ b/documentation/user/en/use/connectors/examples/sealed-instance-example.java @@ -1,3 +1,26 @@ +/* + * + * _ _ ____ ____ + * _____ _(_) |_ __ _| _ \| __ ) + * / _ \ \ / / | __/ _` | | | | _ \ + * | __/\ V /| | || (_| | |_| | |_) | + * \___| \_/ |_|\__\__,_|____/|____/ + * + * Copyright (c) 2023 + * + * 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/main/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. + */ + @Entity( allowedEvolution = { EvolutionMode.ADDING_LOCALES, @@ -27,7 +50,8 @@ public interface Product extends SealedInstance, Seriali name = "manufacturedBefore", description = "How many years ago the product was manufactured.", deprecated = "This attribute is obsolete.", - filterable = true + filterable = true, + nullable = true ) default int[] getYears() { // the default implementation defines default value 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 66e163d83..f3ad07674 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 @@ -439,7 +439,7 @@ Stream testDocumentation() throws IOException { Stream testSingleFileDocumentation() { return this.createTests( Environment.DEMO_SERVER, - getRootDirectory().resolve("documentation/user/en/query/requirements/fetching.md"), + getRootDirectory().resolve("documentation/user/en/use/connectors/java.md"), ExampleFilter.values() ).stream(); } diff --git a/evita_functional_tests/src/test/java/io/evitadb/documentation/mock/Product.java b/evita_functional_tests/src/test/java/io/evitadb/documentation/mock/Product.java index 176b8e282..f7f856d12 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/documentation/mock/Product.java +++ b/evita_functional_tests/src/test/java/io/evitadb/documentation/mock/Product.java @@ -1,3 +1,26 @@ +/* + * + * _ _ ____ ____ + * _____ _(_) |_ __ _| _ \| __ ) + * / _ \ \ / / | __/ _` | | | | _ \ + * | __/\ V /| | || (_| | |_| | |_) | + * \___| \_/ |_|\__\__,_|____/|____/ + * + * Copyright (c) 2023 + * + * 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/main/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.documentation.mock; import io.evitadb.api.requestResponse.data.PriceContract; @@ -50,7 +73,8 @@ public interface Product extends SealedInstance, Seriali name = "manufacturedBefore", description = "How many years ago the product was manufactured.", deprecated = "This attribute is obsolete.", - filterable = true + filterable = true, + nullable = true ) default int[] getYears() { // the default implementation defines default value From 4d466b7e9b24f1f979f34e7fc3ae6f3d3fbe76d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Novotn=C3=BD?= Date: Wed, 13 Dec 2023 16:25:29 +0100 Subject: [PATCH 43/52] docs: added language specifics --- .../user/en/use/connectors/c-sharp.md | 26 +++++++++---------- .../user/en/use/connectors/graphql.md | 6 +++++ documentation/user/en/use/connectors/grpc.md | 8 +++++- documentation/user/en/use/connectors/java.md | 8 +++++- documentation/user/en/use/connectors/rest.md | 8 +++++- 5 files changed, 40 insertions(+), 16 deletions(-) diff --git a/documentation/user/en/use/connectors/c-sharp.md b/documentation/user/en/use/connectors/c-sharp.md index ef9947a59..05a513608 100644 --- a/documentation/user/en/use/connectors/c-sharp.md +++ b/documentation/user/en/use/connectors/c-sharp.md @@ -1,21 +1,20 @@ --- title: C# -perex: +perex: | + Main goal behind the C# driver of evitaDB was to create as similar API as possible to the Java one for the sake of + consistency and to make it easier for developers to switch between languages. However, there are some minor differences + between the two languages, so the C# API is not 100% identical to the Java one. Many of mentioned differences are mostly + semantics and language conventions. date: '10.11.2023' author: 'Ing. Tomáš Pozler' +preferredLang: 'csharp' --- -**Work in progress** - -This article will contain description of C#, mainly the ideas behind its design from the API consumer perspective -(not from the perspective of the C# API developer). It should also contain recommendations and hint how to use -API correctly. - -Main goal behind the C# driver of evitaDB was to create as similar API as possible to the Java one for the sake of -consistency and to make it easier for developers to switch between languages. However, there are some minor differences -between the two languages, so the C# API is not 100% identical to the Java one. Many of mentioned differences are mostly -semantics and language conventions. - + +This chapter describes the C# driver for evitaDB and doesn't make sense for other languages. If you're interested in +the details of the C# implementation, please change your preferred language in the upper right corner. + + This API unification was possible thanks to the common [gRPC](grpc.md) protocol and protobuf data format used by both clients. It is built on top of the same interfaces (especially evita_api/src/main/java/io/evitadb/api/EvitaContract.java and evita_api/src/main/java/io/evitadb/api/EvitaSessionContract.java) for the client and the database itself, @@ -123,4 +122,5 @@ removed. The above intervals are not currently configurable because we believe they are optimal for most use cases. If you need to change them, please contact us with your specific use case and we will consider adding the configuration option. - \ No newline at end of file + + \ No newline at end of file diff --git a/documentation/user/en/use/connectors/graphql.md b/documentation/user/en/use/connectors/graphql.md index 092a8e736..6942953f8 100644 --- a/documentation/user/en/use/connectors/graphql.md +++ b/documentation/user/en/use/connectors/graphql.md @@ -10,6 +10,11 @@ author: 'Lukáš Hornych' preferredLang: 'graphql' --- + +This chapter describes the GraphQL protocol for evitaDB and doesn't make sense for other languages. If you're interested +in the details of the GraphQL implementation, please change your preferred language in the upper right corner. + + The [GraphQL](https://graphql.org/) API has been developed to allow users to easily query domain-specific data from evitaDB with a high degree of customisation of the queries and the self-documentation that GraphQL APIs provide. @@ -190,3 +195,4 @@ Altair has, like many others, has a code-completion in its editor based on the r You can find all the available libraries on the [official GraphQL website](https://graphql.org/code/#language-support) for you to choose from for your own client language. Some can even generate classes/types based on the API schema for you to use in your codebase. + \ No newline at end of file diff --git a/documentation/user/en/use/connectors/grpc.md b/documentation/user/en/use/connectors/grpc.md index 961d25db7..85e5d3ce4 100644 --- a/documentation/user/en/use/connectors/grpc.md +++ b/documentation/user/en/use/connectors/grpc.md @@ -8,6 +8,11 @@ author: 'Ing. Tomáš Pozler' preferredLang: 'java' --- + +This chapter describes the gRPC protocol for evitaDB and relates only to Java or C# drivers. If you're interested +in the details of the gRPC implementation, please change your preferred language in the upper right corner. + + The main idea behind the design was to define a universal communication protocol that follows design of [Java API](https://github.com/FgForrest/evitaDB/tree/dev/evita_api/src/main/java/io/evitadb/api). Unlike [REST](rest.md) and [GraphQL](graphql.md), this API is not intended for direct use by end users / developers, but primarily as a building block for evitaDB drivers or similar tools created for the database consumers. @@ -187,4 +192,5 @@ alternatives for testing gRPC APIs, which can be found [here](https://github.com You can find all the officially maintained libraries on the [gRPC](https://grpc.io) website, from which you can choose a library for your own programming language. Along with a library, you also need to download a suitable protocol [compiler](https://grpc.io/docs/protoc-installation/), which is not directly included in any of the libraries. It comes in the form of plugins, tools or completely separate -libraries - it just depends on the language you are using. \ No newline at end of file +libraries - it just depends on the language you are using. + \ No newline at end of file diff --git a/documentation/user/en/use/connectors/java.md b/documentation/user/en/use/connectors/java.md index bcddf6010..da910f7b3 100644 --- a/documentation/user/en/use/connectors/java.md +++ b/documentation/user/en/use/connectors/java.md @@ -10,6 +10,11 @@ author: 'Ing. Jan Novotný' preferredLang: 'java' --- + +This chapter describes the Java driver for evitaDB and doesn't make sense for other languages. If you're interested +in the details of the Java driver implementation, please change your preferred language in the upper right corner. + + Starting evitaDB in embedded mode is described in detail in chapter [Run evitaDB](../../get-started/run-evitadb?lang=java). Connecting to a remote database instance is described in chapter [Connect to a remote database](../../get-started/query-our-dataset?lang=java). The same applies to [query API](../../use/api/query-data?lang=java) and [write API](../../use/api/write-data?lang=java). @@ -327,4 +332,5 @@ Now we can use the interfaces described above in the following way: The sealed/open principle is a bit more complex than the naive approach of using a single interface for both reading and writing data, but it clearly separates the read and write scenarios, allowing you to maintain control over mutations and -their visibility in a multi-threaded environment. \ No newline at end of file +their visibility in a multi-threaded environment. + \ No newline at end of file diff --git a/documentation/user/en/use/connectors/rest.md b/documentation/user/en/use/connectors/rest.md index 581c6e6e5..acc7d69ff 100644 --- a/documentation/user/en/use/connectors/rest.md +++ b/documentation/user/en/use/connectors/rest.md @@ -10,6 +10,11 @@ author: 'Lukáš Hornych' preferredLang: 'rest' --- + +This chapter describes the REST protocol for evitaDB and doesn't make sense for other languages. If you're interested +in the details of the REST implementation, please change your preferred language in the upper right corner. + + The [REST](https://restfulapi.net/) API with an [OpenAPI schema](https://swagger.io/specification/v3/) in evitaDB has been developed to allow users and developers to easily query domain-specific data from evitaDB via universal well-known API standard that REST APIs provide. @@ -126,4 +131,5 @@ which can even generate clients for your programming language. You can use any basic HTTP client your programming language supports. If you are looking for more, there are code generators that can generate whole typed client in your preferable language from the evitaDB's OpenAPI schemas. -Official way of doing this is by using the [Swagger Codegen](https://swagger.io/tools/swagger-codegen/). \ No newline at end of file +Official way of doing this is by using the [Swagger Codegen](https://swagger.io/tools/swagger-codegen/). + \ No newline at end of file From 2e8d8ce8ea7ac6170f0729eaf6dc070a226d37aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Novotn=C3=BD?= Date: Wed, 13 Dec 2023 16:29:20 +0100 Subject: [PATCH 44/52] docs: added language specifics --- documentation/user/en/use/connectors/java.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/user/en/use/connectors/java.md b/documentation/user/en/use/connectors/java.md index da910f7b3..172d6bc80 100644 --- a/documentation/user/en/use/connectors/java.md +++ b/documentation/user/en/use/connectors/java.md @@ -14,7 +14,7 @@ preferredLang: 'java' This chapter describes the Java driver for evitaDB and doesn't make sense for other languages. If you're interested in the details of the Java driver implementation, please change your preferred language in the upper right corner.
- + Starting evitaDB in embedded mode is described in detail in chapter [Run evitaDB](../../get-started/run-evitadb?lang=java). Connecting to a remote database instance is described in chapter [Connect to a remote database](../../get-started/query-our-dataset?lang=java). The same applies to [query API](../../use/api/query-data?lang=java) and [write API](../../use/api/write-data?lang=java). From f5af31230a80c835f20e515e001d971521092470 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Thu, 14 Dec 2023 10:52:56 +0100 Subject: [PATCH 45/52] docs: typo fix --- documentation/user/en/get-started/create-first-database.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/user/en/get-started/create-first-database.md b/documentation/user/en/get-started/create-first-database.md index 174f7ecc8..6cfd2306b 100644 --- a/documentation/user/en/get-started/create-first-database.md +++ b/documentation/user/en/get-started/create-first-database.md @@ -121,7 +121,7 @@ Once the catalog is created and the schema is known, you can insert a first enti
The session is implicitly opened for the scope of the `updateCatalog` method. The analogous method `queryCatalog` on -the evitaDB contract`UpdateCatalog` method. The analogous method `UueryCatalog` on +the evitaDB contract`UpdateCatalog` method. The analogous method `QueryCatalog` on the evitaDB class level also opens a session, but only in read-only mode, which doesn't allow updating the catalog. Differentiating between read-write and read-only sessions allows evitaDB to optimize query processing and distribute the load in the cluster. From 24ba04b8b391f8aaa1934264b15f8087a2bd897f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Fri, 15 Dec 2023 12:45:54 +0100 Subject: [PATCH 46/52] feat: basic constraint definition export --- .../io/evitadb/documentation/JavaDocCopy.java | 59 +++++++++++++++++++ .../query/require/HierarchyStatistics.java | 9 +-- 2 files changed, 64 insertions(+), 4 deletions(-) diff --git a/evita_functional_tests/src/test/java/io/evitadb/documentation/JavaDocCopy.java b/evita_functional_tests/src/test/java/io/evitadb/documentation/JavaDocCopy.java index ac1164075..8b53e6ef1 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/documentation/JavaDocCopy.java +++ b/evita_functional_tests/src/test/java/io/evitadb/documentation/JavaDocCopy.java @@ -23,6 +23,8 @@ package io.evitadb.documentation; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.thoughtworks.qdox.JavaProjectBuilder; import com.thoughtworks.qdox.model.JavaAnnotation; import com.thoughtworks.qdox.model.JavaClass; @@ -34,6 +36,7 @@ import io.evitadb.api.query.descriptor.annotation.ConstraintDefinition; import io.evitadb.exception.EvitaInternalError; import io.evitadb.test.EvitaTestSupport; +import io.evitadb.utils.StringUtils; import org.junit.jupiter.api.Test; import javax.annotation.Nonnull; @@ -50,6 +53,7 @@ import java.util.Map; import java.util.Optional; import java.util.function.Function; +import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -84,6 +88,11 @@ public class JavaDocCopy implements EvitaTestSupport { */ private static final String QUERY_CONSTRAINTS_CLASS = "io.evitadb.api.query.QueryConstraints"; + /** + * Finds constant with custom name for constraint in class. + */ + private static final Pattern CONSTRAINT_CUSTOM_NAME_PATTERN = Pattern.compile("private +static +final +String +CONSTRAINT_NAME += +\"(\\w+)\";"); + /** * Collects information about all factory methods. */ @@ -270,6 +279,56 @@ void copyConstraintUserDocsLinksToJavaDocs() throws URISyntaxException, IOExcept } } + /** + * Exports all constraint definitions to a JSON file. + */ + @Test + void exportConstraintDefinitions() throws URISyntaxException, IOException { + final Path rootDirectory = getRootDirectory(); + + final JavaProjectBuilder builder = new JavaProjectBuilder(); + + // add all source folders to the QDox library + for (String constraintRoot : CONSTRAINTS_ROOT) { + builder.addSourceTree(rootDirectory.resolve(constraintRoot).toFile()); + } + + final Collection classesByName = builder.getClasses(); + + final ObjectMapper objectMapper = new ObjectMapper(); + final ObjectNode export = objectMapper.createObjectNode(); + for (JavaClass constraintClass : classesByName) { + final Path constraintClassPath = Path.of(constraintClass.getParentSource().getURL().toURI()); + final String constraintSource = Files.readString(constraintClassPath, StandardCharsets.UTF_8); + + final Optional constraintDefinition = constraintClass.getAnnotations() + .stream() + .filter(it -> it.getType().getName().equals(ConstraintDefinition.class.getSimpleName())) + .findFirst(); + if (constraintDefinition.isEmpty()) { + // this class is not a constraint + continue; + } + + final String constraintName; + final Matcher constraintCustomNameMatcher = CONSTRAINT_CUSTOM_NAME_PATTERN.matcher(constraintSource); + if (constraintCustomNameMatcher.find()) { + constraintName = constraintCustomNameMatcher.group(1); + } else { + constraintName = StringUtils.uncapitalize(constraintClass.getName()); + } + final String shortDescription = ((String) constraintDefinition.get().getNamedParameter("shortDescription")).replace("\"", ""); + final String userDocsLink = "https://evitadb.io" + ((String) constraintDefinition.get().getNamedParameter("userDocsLink")).replace("\"", ""); + + final ObjectNode exportedConstraintDefinition = objectMapper.createObjectNode(); + exportedConstraintDefinition.put("shortDescription", shortDescription); + exportedConstraintDefinition.put("userDocsLink", userDocsLink); + export.putIfAbsent(constraintName, exportedConstraintDefinition); + } + + Files.writeString(rootDirectory.resolve("exported-constraints.json"), objectMapper.writeValueAsString(export), StandardCharsets.UTF_8); + } + /** * Describes the locations of the factory methods in the class * diff --git a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyStatistics.java b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyStatistics.java index 9b7326c41..8f8d2fa06 100644 --- a/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyStatistics.java +++ b/evita_query/src/main/java/io/evitadb/api/query/require/HierarchyStatistics.java @@ -78,7 +78,7 @@ * This query actually has to filter and aggregate all the records in the database, which is obviously quite expensive, * even considering that all the indexes are in-memory. Caching is probably the only way out if you really need * to crunch these numbers. - * + * *

Visit detailed user documentation

* * @author Jan Novotný (novotny@fg.cz), FG Forrest a.s. (c) 2023 @@ -91,13 +91,14 @@ ) public class HierarchyStatistics extends AbstractRequireConstraintLeaf implements HierarchyOutputRequireConstraint { @Serial private static final long serialVersionUID = 264601966496432983L; + private static final String CONSTRAINT_NAME = "statistics"; private HierarchyStatistics(@Nonnull Serializable... arguments) { super(arguments); } public HierarchyStatistics() { - super("statistics", StatisticsBase.WITHOUT_USER_FILTER); + super(CONSTRAINT_NAME, StatisticsBase.WITHOUT_USER_FILTER); } public HierarchyStatistics( @@ -106,7 +107,7 @@ public HierarchyStatistics( // because this query can be used only within some other hierarchy query, it would be // unnecessary to duplicate the hierarchy prefix super( - "statistics", + CONSTRAINT_NAME, statisticsBase == null ? StatisticsBase.WITHOUT_USER_FILTER : statisticsBase @@ -121,7 +122,7 @@ public HierarchyStatistics( // because this query can be used only within some other hierarchy query, it would be // unnecessary to duplicate the hierarchy prefix super( - "statistics", + CONSTRAINT_NAME, ArrayUtils.mergeArrays( statisticsBase == null ? new Serializable[] {StatisticsBase.WITHOUT_USER_FILTER} : new Serializable[] {statisticsBase}, From 6b34f91d157ea0c6f53915458c335c87e21a50bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Fri, 15 Dec 2023 16:08:06 +0100 Subject: [PATCH 47/52] feat(#373): support for targetEntity in global entities Basically reverted removed features from 9bff7b59088410d90779fb4a13d401c8e9c26533 and merge it with current features. --- .../model/CatalogDataApiRootDescriptor.java | 4 +- .../dataApi/model/EntityDescriptor.java | 26 +- .../CatalogGraphQLSchemaBuildingContext.java | 11 +- .../CatalogDataApiGraphQLSchemaBuilder.java | 5 +- ...ollectionGraphQLSchemaBuildingContext.java | 2 +- .../dataApi/builder/EntityObjectBuilder.java | 26 +- .../dataApi/model/GlobalEntityDescriptor.java | 53 ++ .../model/GraphQLEntityDescriptor.java | 2 +- .../dataFetcher/EntityDtoTypeResolver.java | 54 ++ .../GetUnknownEntityDataFetcher.java | 89 +- .../ListUnknownEntitiesDataFetcher.java | 92 ++- .../entity/TargetEntityDataFetcher.java | 41 + .../api/resolver/SelectionSetAggregator.java | 213 ++++- .../builder/GenericEntityObjectBuilder.java | 2 +- .../GenericFullResponseObjectBuilder.java | 10 +- .../model/entity/GenericEntityDescriptor.java | 8 + ...alogGraphQLDataEndpointFunctionalTest.java | 32 +- ...QLGetUnknownEntityQueryFunctionalTest.java | 776 +++++++++++++++++- ...istUnknownEntitiesQueryFunctionalTest.java | 670 ++++++++++++++- 19 files changed, 2012 insertions(+), 104 deletions(-) create mode 100644 evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/model/GlobalEntityDescriptor.java create mode 100644 evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/resolver/dataFetcher/EntityDtoTypeResolver.java create mode 100644 evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/resolver/dataFetcher/entity/TargetEntityDataFetcher.java diff --git a/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/api/catalog/dataApi/model/CatalogDataApiRootDescriptor.java b/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/api/catalog/dataApi/model/CatalogDataApiRootDescriptor.java index f38efdd70..a62f5fca9 100644 --- a/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/api/catalog/dataApi/model/CatalogDataApiRootDescriptor.java +++ b/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/api/catalog/dataApi/model/CatalogDataApiRootDescriptor.java @@ -70,7 +70,7 @@ public interface CatalogDataApiRootDescriptor extends CatalogRootDescriptor { .description(""" Finds and returns single entity from unspecified collection by shared arguments between collections. """) - .type(nullableRef(EntityDescriptor.THIS_GLOBAL)) + // type can be entity of any collection .build(); EndpointDescriptor LIST_UNKNOWN_ENTITY = EndpointDescriptor.builder() @@ -80,7 +80,7 @@ public interface CatalogDataApiRootDescriptor extends CatalogRootDescriptor { .description(""" Finds and returns list of entities from unspecified collections by shared arguments between collections. """) - .type(nullableListRef(EntityDescriptor.THIS_GLOBAL)) + // type can be list of entities of any collection .build(); EndpointDescriptor GET_ENTITY = EndpointDescriptor.builder() diff --git a/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/api/catalog/dataApi/model/EntityDescriptor.java b/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/api/catalog/dataApi/model/EntityDescriptor.java index 1f908aec7..35e956bb6 100644 --- a/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/api/catalog/dataApi/model/EntityDescriptor.java +++ b/evita_external_api/evita_external_api_core/src/main/java/io/evitadb/externalApi/api/catalog/dataApi/model/EntityDescriptor.java @@ -29,9 +29,6 @@ import io.evitadb.externalApi.api.model.ObjectDescriptor; import io.evitadb.externalApi.api.model.PropertyDescriptor; -import java.util.List; -import java.util.Locale; - import static io.evitadb.externalApi.api.catalog.dataApi.model.CatalogDataApiRootDescriptor.LOCALE_ENUM; import static io.evitadb.externalApi.api.model.ObjectPropertyDataTypeDescriptor.nonNullListRef; import static io.evitadb.externalApi.api.model.ObjectPropertyDataTypeDescriptor.nullableRef; @@ -123,20 +120,27 @@ The moment is either extracted from the query as well (if present) or current da .type(nonNull(PriceInnerRecordHandling.class)) .build(); - ObjectDescriptor THIS_REFERENCE = ObjectDescriptor.builder() - .name("EntityReference") + ObjectDescriptor THIS_CLASSIFIER = ObjectDescriptor.builder() + .name("Entity") .description(""" - Pointer to a full entity. + Generic the most basic entity. + Common ancestor for all specific entities which correspond to specific collections. """) - .staticFields(List.of(PRIMARY_KEY, TYPE, VERSION)) + .staticField(PRIMARY_KEY) + .staticField(TYPE) + .staticField(VERSION) .build(); - ObjectDescriptor THIS_GLOBAL = ObjectDescriptor.extend(THIS_REFERENCE) - .name("Entity") + /** + * Used only to distinguish from entity classifier for clarity, that this is a final object that just references an + * entity, not that an entity should extend this. + */ + ObjectDescriptor THIS_REFERENCE = ObjectDescriptor.extend(THIS_CLASSIFIER) + .name("EntityReference") .description(""" - Catalog-wise entity with only common data across all entity collections. + Pointer to a full entity. """) .build(); - ObjectDescriptor THIS = ObjectDescriptor.extend(THIS_REFERENCE) + ObjectDescriptor THIS = ObjectDescriptor.extend(THIS_CLASSIFIER) .name("*") .build(); } diff --git a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/builder/CatalogGraphQLSchemaBuildingContext.java b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/builder/CatalogGraphQLSchemaBuildingContext.java index 2f176c30d..0911dd972 100644 --- a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/builder/CatalogGraphQLSchemaBuildingContext.java +++ b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/builder/CatalogGraphQLSchemaBuildingContext.java @@ -37,8 +37,10 @@ import javax.annotation.Nonnull; import java.util.Currency; import java.util.Locale; +import java.util.Map; import java.util.Set; +import static io.evitadb.utils.CollectionUtils.createHashMap; import static io.evitadb.utils.CollectionUtils.createHashSet; /** @@ -52,14 +54,15 @@ public class CatalogGraphQLSchemaBuildingContext extends GraphQLSchemaBuildingCo @Getter @Nonnull private final Set supportedLocales; @Getter @Nonnull private final Set supportedCurrencies; @Getter @Nonnull private final Set entitySchemas; + @Getter @Nonnull private final Map entityTypeToEntityObject; public CatalogGraphQLSchemaBuildingContext(@Nonnull GraphQLConfig config, @Nonnull Evita evita, @Nonnull CatalogContract catalog) { super(config, evita); this.catalog = catalog; - this.supportedLocales = createHashSet(20); - this.supportedCurrencies = createHashSet(20); + this.supportedLocales = createHashSet(10); + this.supportedCurrencies = createHashSet(10); this.entitySchemas = evita.queryCatalog(catalog.getName(), session -> { final Set collections = session.getAllEntityTypes(); @@ -73,6 +76,7 @@ public CatalogGraphQLSchemaBuildingContext(@Nonnull GraphQLConfig config, }); return schemas; }); + this.entityTypeToEntityObject = createHashMap(this.entitySchemas.size()); } @Nonnull @@ -80,7 +84,8 @@ public CatalogSchemaContract getSchema() { return catalog.getSchema(); } - public void registerEntityObject(@Nonnull GraphQLObjectType entityObject) { + public void registerEntityObject(@Nonnull String entityType, @Nonnull GraphQLObjectType entityObject) { registerType(entityObject); + entityTypeToEntityObject.putIfAbsent(entityType, entityObject); } } diff --git a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogDataApiGraphQLSchemaBuilder.java b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogDataApiGraphQLSchemaBuilder.java index c65cd508e..c4306d5c0 100644 --- a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogDataApiGraphQLSchemaBuilder.java +++ b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogDataApiGraphQLSchemaBuilder.java @@ -53,6 +53,7 @@ import io.evitadb.externalApi.graphql.api.catalog.dataApi.builder.constraint.RequireConstraintSchemaBuilder; import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.DeleteEntitiesMutationHeaderDescriptor; import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.GetEntityHeaderDescriptor; +import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.GlobalEntityDescriptor; import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.ListEntitiesHeaderDescriptor; import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.ListUnknownEntitiesHeaderDescriptor; import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.QueryEntitiesHeaderDescriptor; @@ -263,7 +264,8 @@ private BuiltFieldDescriptor buildCollectionsField() { @Nullable private BuiltFieldDescriptor buildGetUnknownEntityField() { final GraphQLFieldDefinition.Builder getUnknownEntityFieldBuilder = CatalogDataApiRootDescriptor.GET_UNKNOWN_ENTITY - .to(staticEndpointBuilderTransformer); + .to(staticEndpointBuilderTransformer) + .type(typeRef(GlobalEntityDescriptor.THIS.name())); // build globally unique attribute filters final List globalAttributes = buildingContext.getCatalog() @@ -314,6 +316,7 @@ private BuiltFieldDescriptor buildGetUnknownEntityField() { private BuiltFieldDescriptor buildListUnknownEntityField() { final Builder listUnknownEntityFieldBuilder = CatalogDataApiRootDescriptor.LIST_UNKNOWN_ENTITY .to(staticEndpointBuilderTransformer) + .type(nonNull(list(nonNull(typeRef(GlobalEntityDescriptor.THIS.name()))))) .argument(ListUnknownEntitiesHeaderDescriptor.LIMIT.to(argumentBuilderTransformer)); // build globally unique attribute filters diff --git a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/builder/CollectionGraphQLSchemaBuildingContext.java b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/builder/CollectionGraphQLSchemaBuildingContext.java index f674f3c4a..ccc7b95f0 100644 --- a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/builder/CollectionGraphQLSchemaBuildingContext.java +++ b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/builder/CollectionGraphQLSchemaBuildingContext.java @@ -58,7 +58,7 @@ public CatalogContract getCatalog() { } public void registerEntityObject(@Nonnull GraphQLObjectType entityObject) { - catalogCtx.registerEntityObject(entityObject); + catalogCtx.registerEntityObject(schema.getName(), entityObject); } /** diff --git a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/builder/EntityObjectBuilder.java b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/builder/EntityObjectBuilder.java index 61326e459..c1d53ec08 100644 --- a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/builder/EntityObjectBuilder.java +++ b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/builder/EntityObjectBuilder.java @@ -27,6 +27,7 @@ import graphql.schema.DataFetcher; import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLInputType; +import graphql.schema.GraphQLInterfaceType; import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLOutputType; import graphql.schema.GraphQLType; @@ -51,6 +52,7 @@ import io.evitadb.externalApi.graphql.api.catalog.dataApi.builder.constraint.GraphQLConstraintSchemaBuildingContext; import io.evitadb.externalApi.graphql.api.catalog.dataApi.builder.constraint.OrderConstraintSchemaBuilder; import io.evitadb.externalApi.graphql.api.catalog.dataApi.builder.constraint.RequireConstraintSchemaBuilder; +import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.GlobalEntityDescriptor; import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.GraphQLEntityDescriptor; import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.entity.AssociatedDataFieldHeaderDescriptor; import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.entity.AttributesFieldHeaderDescriptor; @@ -60,6 +62,7 @@ import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.entity.PricesFieldHeaderDescriptor; import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.entity.ReferenceFieldHeaderDescriptor; import io.evitadb.externalApi.graphql.api.catalog.dataApi.resolver.dataFetcher.BigDecimalDataFetcher; +import io.evitadb.externalApi.graphql.api.catalog.dataApi.resolver.dataFetcher.EntityDtoTypeResolver; import io.evitadb.externalApi.graphql.api.catalog.dataApi.resolver.dataFetcher.entity.*; import io.evitadb.externalApi.graphql.api.dataType.DataTypesConverter; import io.evitadb.externalApi.graphql.api.model.ObjectDescriptorToGraphQLInterfaceTransformer; @@ -130,6 +133,12 @@ public EntityObjectBuilder(@Nonnull CatalogGraphQLSchemaBuildingContext building } public void buildCommonTypes() { + final GraphQLInterfaceType entityClassifier = EntityDescriptor.THIS_CLASSIFIER.to(interfaceBuilderTransformer).build(); + buildingContext.registerType(entityClassifier); + buildingContext.registerTypeResolver( + entityClassifier, + new EntityDtoTypeResolver(buildingContext.getEntityTypeToEntityObject()) + ); buildingContext.registerType(EntityDescriptor.THIS_REFERENCE.to(objectBuilderTransformer).build()); buildingContext.registerType(buildPriceObject()); buildingContext.registerType(buildGlobal()); @@ -155,7 +164,8 @@ public GraphQLObjectType build(@Nonnull CollectionGraphQLSchemaBuildingContext c final GraphQLObjectType.Builder entityObjectBuilder = entityDescriptor .to(objectBuilderTransformer) .name(objectName) - .description(entitySchema.getDescription()); + .description(entitySchema.getDescription()) + .withInterface(typeRef(GraphQLEntityDescriptor.THIS_CLASSIFIER.name())); // build locale fields if (!entitySchema.getLocales().isEmpty()) { @@ -236,21 +246,27 @@ public GraphQLObjectType build(@Nonnull CollectionGraphQLSchemaBuildingContext c private GraphQLObjectType buildGlobal() { final CatalogSchemaContract catalogSchema = buildingContext.getSchema(); - final GraphQLObjectType.Builder globalEntityObjectBuilder = GraphQLEntityDescriptor.THIS_GLOBAL.to(objectBuilderTransformer); + final GraphQLObjectType.Builder globalEntityObjectBuilder = GlobalEntityDescriptor.THIS.to(objectBuilderTransformer); if (!buildingContext.getSupportedLocales().isEmpty()) { - globalEntityObjectBuilder.field(GraphQLEntityDescriptor.LOCALES.to(fieldBuilderTransformer)); - globalEntityObjectBuilder.field(GraphQLEntityDescriptor.ALL_LOCALES.to(fieldBuilderTransformer)); + globalEntityObjectBuilder.field(GlobalEntityDescriptor.LOCALES.to(fieldBuilderTransformer)); + globalEntityObjectBuilder.field(GlobalEntityDescriptor.ALL_LOCALES.to(fieldBuilderTransformer)); } if (!catalogSchema.getAttributes().isEmpty()) { buildingContext.registerFieldToObject( - GraphQLEntityDescriptor.THIS_GLOBAL, + GlobalEntityDescriptor.THIS, globalEntityObjectBuilder, buildGlobalEntityAttributesField() ); } + buildingContext.registerDataFetcher( + GlobalEntityDescriptor.THIS, + GlobalEntityDescriptor.TARGET_ENTITY, + new TargetEntityDataFetcher() + ); + return globalEntityObjectBuilder.build(); } diff --git a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/model/GlobalEntityDescriptor.java b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/model/GlobalEntityDescriptor.java new file mode 100644 index 000000000..01cd9e3b6 --- /dev/null +++ b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/model/GlobalEntityDescriptor.java @@ -0,0 +1,53 @@ +/* + * + * _ _ ____ ____ + * _____ _(_) |_ __ _| _ \| __ ) + * / _ \ \ / / | __/ _` | | | | _ \ + * | __/\ V /| | || (_| | |_| | |_) | + * \___| \_/ |_|\__\__,_|____/|____/ + * + * Copyright (c) 2023 + * + * 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/main/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.externalApi.graphql.api.catalog.dataApi.model; + +import io.evitadb.externalApi.api.model.ObjectDescriptor; +import io.evitadb.externalApi.api.model.PropertyDescriptor; + +import static io.evitadb.externalApi.api.model.ObjectPropertyDataTypeDescriptor.nonNullRef; + +/** + * Represents global entity with only fields that are present in all entities across all collection.s + * + * @author Lukáš Hornych, FG Forrest a.s. (c) 2023 + */ +public interface GlobalEntityDescriptor extends GraphQLEntityDescriptor { + + PropertyDescriptor TARGET_ENTITY = PropertyDescriptor.builder() + .name("targetEntity") + .description(""" + Contains a reference to actual collection-specific entity represented by this global entity holder. + """) + .type(nonNullRef(THIS_CLASSIFIER)) + .build(); + + ObjectDescriptor THIS = ObjectDescriptor.extend(THIS_CLASSIFIER) + .name("GlobalEntity") + .description(""" + Catalog-wise entity with only common data across all entity collections. + """) + .staticField(TARGET_ENTITY) + .build(); +} diff --git a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/model/GraphQLEntityDescriptor.java b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/model/GraphQLEntityDescriptor.java index 99110eb52..417528881 100644 --- a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/model/GraphQLEntityDescriptor.java +++ b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/model/GraphQLEntityDescriptor.java @@ -59,7 +59,7 @@ Each entity must be part of at most single hierarchy (tree). // type is expected to be a list of non-hierarchical version of this entity .build(); - ObjectDescriptor THIS_NON_HIERARCHICAL = ObjectDescriptor.extend(THIS_REFERENCE) + ObjectDescriptor THIS_NON_HIERARCHICAL = ObjectDescriptor.extend(THIS_CLASSIFIER) .name("NonHierarchical*") .build(); } diff --git a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/resolver/dataFetcher/EntityDtoTypeResolver.java b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/resolver/dataFetcher/EntityDtoTypeResolver.java new file mode 100644 index 000000000..b05388e81 --- /dev/null +++ b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/resolver/dataFetcher/EntityDtoTypeResolver.java @@ -0,0 +1,54 @@ +/* + * + * _ _ ____ ____ + * _____ _(_) |_ __ _| _ \| __ ) + * / _ \ \ / / | __/ _` | | | | _ \ + * | __/\ V /| | || (_| | |_| | |_) | + * \___| \_/ |_|\__\__,_|____/|____/ + * + * Copyright (c) 2023 + * + * 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/main/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.externalApi.graphql.api.catalog.dataApi.resolver.dataFetcher; + +import graphql.TypeResolutionEnvironment; +import graphql.schema.GraphQLObjectType; +import graphql.schema.TypeResolver; +import io.evitadb.api.requestResponse.data.EntityClassifier; +import io.evitadb.externalApi.graphql.exception.GraphQLQueryResolvingInternalError; +import lombok.RequiredArgsConstructor; + +import javax.annotation.Nonnull; +import java.util.Map; +import java.util.Optional; + +/** + * Resolve specific entity DTO for entity interface based on fetched original entity object. + * + * @author Lukáš Hornych, FG Forrest a.s. (c) 2022 + */ +@RequiredArgsConstructor +public class EntityDtoTypeResolver implements TypeResolver { + + private final Map entityTypeToEntityDtoMapping; + + @Nonnull + @Override + public GraphQLObjectType getType(@Nonnull TypeResolutionEnvironment env) { + final EntityClassifier entity = env.getObject(); + return Optional.ofNullable(entityTypeToEntityDtoMapping.get(entity.getType())) + .orElseThrow(() -> new GraphQLQueryResolvingInternalError("Missing entity dto for entity type `" + entity.getType() + "`.")); + } +} \ No newline at end of file diff --git a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/resolver/dataFetcher/GetUnknownEntityDataFetcher.java b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/resolver/dataFetcher/GetUnknownEntityDataFetcher.java index 4345e37be..819f321c9 100644 --- a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/resolver/dataFetcher/GetUnknownEntityDataFetcher.java +++ b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/resolver/dataFetcher/GetUnknownEntityDataFetcher.java @@ -26,18 +26,23 @@ import graphql.execution.DataFetcherResult; import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; +import graphql.schema.SelectedField; import io.evitadb.api.EvitaSessionContract; import io.evitadb.api.query.FilterConstraint; import io.evitadb.api.query.Query; import io.evitadb.api.query.filter.FilterBy; +import io.evitadb.api.query.require.EntityContentRequire; import io.evitadb.api.query.require.EntityFetch; import io.evitadb.api.query.require.Require; import io.evitadb.api.requestResponse.data.EntityClassifier; +import io.evitadb.api.requestResponse.data.SealedEntity; import io.evitadb.api.requestResponse.data.structure.EntityDecorator; import io.evitadb.api.requestResponse.data.structure.EntityReference; import io.evitadb.api.requestResponse.schema.CatalogSchemaContract; +import io.evitadb.api.requestResponse.schema.EntitySchemaContract; import io.evitadb.api.requestResponse.schema.GlobalAttributeSchemaContract; import io.evitadb.externalApi.graphql.api.catalog.GraphQLContextKey; +import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.GlobalEntityDescriptor; import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.QueryHeaderArgumentsJoinType; import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.UnknownEntityHeaderDescriptor; import io.evitadb.externalApi.graphql.api.catalog.dataApi.resolver.constraint.EntityFetchRequireResolver; @@ -54,7 +59,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.Serializable; -import java.util.HashMap; +import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Locale; @@ -62,10 +67,12 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; import static io.evitadb.api.query.Query.query; import static io.evitadb.api.query.QueryConstraints.*; import static io.evitadb.externalApi.api.ExternalApiNamingConventions.ARGUMENT_NAME_NAMING_CONVENTION; +import static io.evitadb.externalApi.api.ExternalApiNamingConventions.TYPE_NAME_NAMING_CONVENTION; import static io.evitadb.utils.CollectionUtils.createHashMap; /** @@ -79,7 +86,7 @@ * @author Lukáš Hornych, FG Forrest a.s. (c) 2022 */ @Slf4j -public class GetUnknownEntityDataFetcher implements DataFetcher> { +public class GetUnknownEntityDataFetcher implements DataFetcher> { /** * Schema of catalog to which this fetcher is mapped to. @@ -92,6 +99,11 @@ public class GetUnknownEntityDataFetcher implements DataFetcher allPossibleLocales; + @Nonnull + private final Function entitySchemaFetcher; + @Nonnull + private final Map entityDtoObjectTypeNameByEntityType; + @Nonnull private final EntityFetchRequireResolver entityFetchRequireResolver; public GetUnknownEntityDataFetcher(@Nonnull CatalogSchemaContract catalogSchema, @@ -99,6 +111,13 @@ public GetUnknownEntityDataFetcher(@Nonnull CatalogSchemaContract catalogSchema, this.catalogSchema = catalogSchema; this.allPossibleLocales = allPossibleLocales; + this.entitySchemaFetcher = catalogSchema::getEntitySchemaOrThrowException; + this.entityDtoObjectTypeNameByEntityType = createHashMap(catalogSchema.getEntitySchemas().size()); + catalogSchema.getEntitySchemas().forEach(entitySchema -> this.entityDtoObjectTypeNameByEntityType.put( + entitySchema.getName(), + entitySchema.getNameVariant(TYPE_NAME_NAMING_CONVENTION) + )); + final FilterConstraintResolver filterConstraintResolver = new FilterConstraintResolver(catalogSchema); final OrderConstraintResolver orderConstraintResolver = new OrderConstraintResolver(catalogSchema); final RequireConstraintResolver requireConstraintResolver = new RequireConstraintResolver( @@ -106,9 +125,7 @@ public GetUnknownEntityDataFetcher(@Nonnull CatalogSchemaContract catalogSchema, new AtomicReference<>(filterConstraintResolver) ); this.entityFetchRequireResolver = new EntityFetchRequireResolver( - __ -> { - throw new GraphQLInternalError("Global entity shouldn't need to fetch other entities. This should never happen."); - }, + entitySchemaFetcher, filterConstraintResolver, orderConstraintResolver, requireConstraintResolver @@ -117,11 +134,11 @@ public GetUnknownEntityDataFetcher(@Nonnull CatalogSchemaContract catalogSchema, @Nonnull @Override - public DataFetcherResult get(@Nonnull DataFetchingEnvironment environment) { + public DataFetcherResult get(@Nonnull DataFetchingEnvironment environment) { final Arguments arguments = Arguments.from(environment, catalogSchema); final FilterBy filterBy = buildFilterBy(arguments); - final Require require = buildRequire(environment); + final Require require = buildInitialRequire(environment); final Query query = query( filterBy, require @@ -130,11 +147,27 @@ public DataFetcherResult get(@Nonnull DataFetchingEnvironment final EvitaSessionContract evitaSession = environment.getGraphQlContext().get(GraphQLContextKey.EVITA_SESSION); - final DataFetcherResult.Builder resultBuilder = DataFetcherResult.newResult(); - evitaSession.queryOne(query, EntityClassifier.class) - .ifPresent(entity -> resultBuilder - .data(entity) - .localContext(EntityQueryContext.empty())); + final SealedEntity loadedEntity = evitaSession.queryOne(query, SealedEntity.class) + .map(it -> { + final String entityType = it.getType(); + final Optional contentRequires = buildEnrichingRequires(environment, entityType); + if (contentRequires.isEmpty()) { + log.debug("Skipping enriching entity reference `{}`. Target entity not requested.", it); + return it; + } else { + log.debug("Enriching entity reference `{}` with `{}`.", it, Arrays.toString(contentRequires.get())); + return evitaSession.enrichEntity(it, contentRequires.get()); + } + }) + .orElse(null); + + final DataFetcherResult.Builder resultBuilder = DataFetcherResult.newResult(); + if (loadedEntity != null) { + resultBuilder + .data(loadedEntity) + .localContext(EntityQueryContext.empty()); + } + return resultBuilder.build(); } @@ -159,18 +192,46 @@ private > FilterBy buildFilterBy(@Nonnull } @Nonnull - private Require buildRequire(@Nonnull DataFetchingEnvironment environment) { + private Require buildInitialRequire(@Nonnull DataFetchingEnvironment environment) { final EntityFetch entityFetch = entityFetchRequireResolver.resolveEntityFetch( SelectionSetAggregator.from(environment.getSelectionSet()), null, catalogSchema, allPossibleLocales ) - .orElse(null); + // we need to have at least a body, so we can enrich it later if needed + .orElse(entityFetch()); return require(entityFetch); } + @Nonnull + private Optional buildEnrichingRequires(@Nonnull DataFetchingEnvironment environment, + @Nonnull String entityType) { + final List targetEntityFields = SelectionSetAggregator.getImmediateFields( + GlobalEntityDescriptor.TARGET_ENTITY.name(), + environment.getSelectionSet() + ); + if (targetEntityFields.isEmpty()) { + return Optional.empty(); + } + + final Optional entityFetch = entityFetchRequireResolver.resolveEntityFetch( + SelectionSetAggregator.from( + targetEntityFields.stream().map(SelectedField::getSelectionSet).toList(), + entityDtoObjectTypeNameByEntityType.get(entityType) + ), + null, + entitySchemaFetcher.apply(entityType) + ); + + return Optional.of( + entityFetch + .map(EntityFetch::getRequirements) + .orElse(new EntityContentRequire[0]) + ); + } + /** * Holds parsed GraphQL query arguments relevant for single entity query */ diff --git a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/resolver/dataFetcher/ListUnknownEntitiesDataFetcher.java b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/resolver/dataFetcher/ListUnknownEntitiesDataFetcher.java index a02aa9f24..fd1532f0d 100644 --- a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/resolver/dataFetcher/ListUnknownEntitiesDataFetcher.java +++ b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/resolver/dataFetcher/ListUnknownEntitiesDataFetcher.java @@ -26,18 +26,24 @@ import graphql.execution.DataFetcherResult; import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; +import graphql.schema.SelectedField; import io.evitadb.api.EvitaSessionContract; import io.evitadb.api.query.FilterConstraint; import io.evitadb.api.query.Query; import io.evitadb.api.query.RequireConstraint; import io.evitadb.api.query.filter.FilterBy; +import io.evitadb.api.query.require.EntityContentRequire; +import io.evitadb.api.query.require.EntityFetch; import io.evitadb.api.query.require.Require; import io.evitadb.api.requestResponse.data.EntityClassifier; +import io.evitadb.api.requestResponse.data.SealedEntity; import io.evitadb.api.requestResponse.data.structure.EntityDecorator; import io.evitadb.api.requestResponse.data.structure.EntityReference; import io.evitadb.api.requestResponse.schema.CatalogSchemaContract; +import io.evitadb.api.requestResponse.schema.EntitySchemaContract; import io.evitadb.api.requestResponse.schema.GlobalAttributeSchemaContract; import io.evitadb.externalApi.graphql.api.catalog.GraphQLContextKey; +import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.GlobalEntityDescriptor; import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.ListUnknownEntitiesHeaderDescriptor; import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.QueryHeaderArgumentsJoinType; import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.UnknownEntityHeaderDescriptor; @@ -56,7 +62,7 @@ import javax.annotation.Nullable; import java.io.Serializable; import java.lang.reflect.Array; -import java.util.HashMap; +import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Locale; @@ -64,10 +70,12 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; import static io.evitadb.api.query.Query.query; import static io.evitadb.api.query.QueryConstraints.*; import static io.evitadb.externalApi.api.ExternalApiNamingConventions.ARGUMENT_NAME_NAMING_CONVENTION; +import static io.evitadb.externalApi.api.ExternalApiNamingConventions.TYPE_NAME_NAMING_CONVENTION; import static io.evitadb.utils.CollectionUtils.createHashMap; /** @@ -81,7 +89,7 @@ * @author Lukáš Hornych, FG Forrest a.s. (c) 2022 */ @Slf4j -public class ListUnknownEntitiesDataFetcher implements DataFetcher>> { +public class ListUnknownEntitiesDataFetcher implements DataFetcher>> { /** * Schema of catalog to which this fetcher is mapped to. @@ -94,6 +102,11 @@ public class ListUnknownEntitiesDataFetcher implements DataFetcher allPossibleLocales; + @Nonnull + private final Function entitySchemaFetcher; + @Nonnull + private final Map entityDtoObjectTypeNameByEntityType; + @Nonnull private final EntityFetchRequireResolver entityFetchRequireResolver; public ListUnknownEntitiesDataFetcher(@Nonnull CatalogSchemaContract catalogSchema, @@ -101,6 +114,13 @@ public ListUnknownEntitiesDataFetcher(@Nonnull CatalogSchemaContract catalogSche this.catalogSchema = catalogSchema; this.allPossibleLocales = allPossibleLocales; + this.entitySchemaFetcher = catalogSchema::getEntitySchemaOrThrowException; + this.entityDtoObjectTypeNameByEntityType = createHashMap(catalogSchema.getEntitySchemas().size()); + catalogSchema.getEntitySchemas().forEach(entitySchema -> this.entityDtoObjectTypeNameByEntityType.put( + entitySchema.getName(), + entitySchema.getNameVariant(TYPE_NAME_NAMING_CONVENTION) + )); + final FilterConstraintResolver filterConstraintResolver = new FilterConstraintResolver(catalogSchema); final OrderConstraintResolver orderConstraintResolver = new OrderConstraintResolver(catalogSchema); final RequireConstraintResolver requireConstraintResolver = new RequireConstraintResolver( @@ -108,9 +128,7 @@ public ListUnknownEntitiesDataFetcher(@Nonnull CatalogSchemaContract catalogSche new AtomicReference<>(filterConstraintResolver) ); this.entityFetchRequireResolver = new EntityFetchRequireResolver( - __ -> { - throw new GraphQLInternalError("Global entity shouldn't need to fetch other entities. This should never happen."); - }, + entitySchemaFetcher, filterConstraintResolver, orderConstraintResolver, requireConstraintResolver @@ -120,11 +138,11 @@ public ListUnknownEntitiesDataFetcher(@Nonnull CatalogSchemaContract catalogSche @Nonnull @Override - public DataFetcherResult> get(@Nonnull DataFetchingEnvironment environment) { + public DataFetcherResult> get(@Nonnull DataFetchingEnvironment environment) { final Arguments arguments = Arguments.from(environment, catalogSchema); final FilterBy filterBy = buildFilterBy(arguments); - final Require require = buildRequire(environment, arguments); + final Require require = buildInitialRequire(environment, arguments); final Query query = query( filterBy, require @@ -134,11 +152,32 @@ public DataFetcherResult> get(@Nonnull DataFetchingEnviro final EvitaSessionContract evitaSession = environment.getGraphQlContext().get(GraphQLContextKey.EVITA_SESSION); final List entities = evitaSession.queryList(query, EntityClassifier.class); - final DataFetcherResult.Builder> resultBuilder = DataFetcherResult.>newResult() - .data(entities); + final List entityReferences = evitaSession.queryList(query, SealedEntity.class); + final List loadedEntities; + if (entityReferences.isEmpty()) { + loadedEntities = List.of(); + } else { + loadedEntities = entityReferences.stream() + .map(it -> { + final String entityType = it.getType(); + final Optional contentRequires = buildEnrichingRequires(environment, entityType); + if (contentRequires.isEmpty()) { + log.debug("Skipping enriching entity reference `{}`. Target entity not requested.", it); + return it; + } else { + log.debug("Enriching entity reference `{}` with `{}`.", it, Arrays.toString(contentRequires.get())); + return evitaSession.enrichEntity(it, contentRequires.get()); + } + }) + .toList(); + } + + final DataFetcherResult.Builder> resultBuilder = DataFetcherResult.>newResult() + .data(loadedEntities); if (!entities.isEmpty()) { resultBuilder.localContext(EntityQueryContext.empty()); } + return resultBuilder.build(); } @@ -165,7 +204,7 @@ private > FilterBy buildFilterBy(@Nonnull } @Nonnull - private Require buildRequire(@Nonnull DataFetchingEnvironment environment, @Nonnull Arguments arguments) { + private Require buildInitialRequire(@Nonnull DataFetchingEnvironment environment, @Nonnull Arguments arguments) { final List requireConstraints = new LinkedList<>(); entityFetchRequireResolver.resolveEntityFetch( @@ -174,7 +213,11 @@ private Require buildRequire(@Nonnull DataFetchingEnvironment environment, @Nonn catalogSchema, allPossibleLocales ) - .ifPresent(requireConstraints::add); + .ifPresentOrElse( + requireConstraints::add, + // we need to have at least a body, so we can enrich it later if needed + () -> requireConstraints.add(entityFetch()) + ); if (arguments.limit() != null) { requireConstraints.add(strip(0, arguments.limit())); @@ -185,6 +228,33 @@ private Require buildRequire(@Nonnull DataFetchingEnvironment environment, @Nonn ); } + @Nonnull + private Optional buildEnrichingRequires(@Nonnull DataFetchingEnvironment environment, + @Nonnull String entityType) { + final List targetEntityFields = SelectionSetAggregator.getImmediateFields( + GlobalEntityDescriptor.TARGET_ENTITY.name(), + environment.getSelectionSet() + ); + if (targetEntityFields.isEmpty()) { + return Optional.empty(); + } + + final Optional entityFetch = entityFetchRequireResolver.resolveEntityFetch( + SelectionSetAggregator.from( + targetEntityFields.stream().map(SelectedField::getSelectionSet).toList(), + entityDtoObjectTypeNameByEntityType.get(entityType) + ), + null, + entitySchemaFetcher.apply(entityType) + ); + + return Optional.of( + entityFetch + .map(EntityFetch::getRequirements) + .orElse(new EntityContentRequire[0]) + ); + } + /** * Holds parsed GraphQL query arguments relevant for single entity query */ diff --git a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/resolver/dataFetcher/entity/TargetEntityDataFetcher.java b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/resolver/dataFetcher/entity/TargetEntityDataFetcher.java new file mode 100644 index 000000000..33e64b83e --- /dev/null +++ b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/resolver/dataFetcher/entity/TargetEntityDataFetcher.java @@ -0,0 +1,41 @@ +/* + * + * _ _ ____ ____ + * _____ _(_) |_ __ _| _ \| __ ) + * / _ \ \ / / | __/ _` | | | | _ \ + * | __/\ V /| | || (_| | |_| | |_) | + * \___| \_/ |_|\__\__,_|____/|____/ + * + * Copyright (c) 2023 + * + * 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/main/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.externalApi.graphql.api.catalog.dataApi.resolver.dataFetcher.entity; + +import graphql.schema.DataFetcher; +import graphql.schema.DataFetchingEnvironment; +import io.evitadb.api.requestResponse.data.EntityClassifier; + +/** + * TODO lho docs + * + * @author Lukáš Hornych, 2023 + */ +public class TargetEntityDataFetcher implements DataFetcher { + @Override + public EntityClassifier get(DataFetchingEnvironment environment) throws Exception { + // we just pass the global entity as the target entity, because the global entity is the target entity on the server side + return environment.getSource(); + } +} diff --git a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/resolver/SelectionSetAggregator.java b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/resolver/SelectionSetAggregator.java index 1ee5738fa..63cf3f10e 100644 --- a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/resolver/SelectionSetAggregator.java +++ b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/resolver/SelectionSetAggregator.java @@ -53,19 +53,44 @@ public class SelectionSetAggregator { @Nullable private final DataFetchingFieldSelectionSet originalSelectionSet; @Nullable private final List originalSelectionSets; + @Nullable private final String entityDtoObjectTypeName; /** * Creates wrapper without filtering of fields. Functions same as original {@link DataFetchingFieldSelectionSet}. */ public static SelectionSetAggregator from(@Nonnull DataFetchingFieldSelectionSet originalSelectionSet) { - return new SelectionSetAggregator(originalSelectionSet, null); + return new SelectionSetAggregator(originalSelectionSet, null, null); } /** * Creates wrapper without filtering of fields. Functions same as original {@link DataFetchingFieldSelectionSet}. */ public static SelectionSetAggregator from(@Nonnull List originalSelectionSets) { - return new SelectionSetAggregator(null, originalSelectionSets); + return new SelectionSetAggregator(null, originalSelectionSets, null); + } + + /** + * Creates wrapper with filtering of fields of original {@link DataFetchingFieldSelectionSet} to fields for + * {@code entityDtoObjectTypeName}. + */ + public static SelectionSetAggregator from(@Nonnull DataFetchingFieldSelectionSet originalSelectionSet, @Nonnull String entityDtoObjectTypeName) { + return new SelectionSetAggregator( + originalSelectionSet, + null, + entityDtoObjectTypeName + ); + } + + /** + * Creates wrapper with filtering of fields of original {@link DataFetchingFieldSelectionSet} to fields for + * {@code entityDtoObjectTypeName}. + */ + public static SelectionSetAggregator from(@Nonnull List originalSelectionSets, @Nonnull String entityDtoObjectTypeName) { + return new SelectionSetAggregator( + null, + originalSelectionSets, + entityDtoObjectTypeName + ); } /** @@ -74,9 +99,9 @@ public static SelectionSetAggregator from(@Nonnull List getImmediateFields() { if (originalSelectionSet != null) { - return getImmediateFields(originalSelectionSet); + return getImmediateFields(originalSelectionSet, entityDtoObjectTypeName); } else { - return getImmediateFields(originalSelectionSets); + return getImmediateFields(originalSelectionSets, entityDtoObjectTypeName); } } @@ -98,9 +123,9 @@ public List getImmediateFields() { @Nonnull public List getImmediateFields(@Nonnull String fieldName) { if (originalSelectionSet != null) { - return getImmediateFields(fieldName, originalSelectionSet); + return getImmediateFields(fieldName, originalSelectionSet, entityDtoObjectTypeName); } else { - return getImmediateFields(fieldName, originalSelectionSets); + return getImmediateFields(fieldName, originalSelectionSets, entityDtoObjectTypeName); } } @@ -110,9 +135,9 @@ public List getImmediateFields(@Nonnull String fieldName) { @Nonnull public List getImmediateFields(@Nonnull Set fieldNames) { if (originalSelectionSet != null) { - return getImmediateFields(fieldNames, originalSelectionSet); + return getImmediateFields(fieldNames, originalSelectionSet, entityDtoObjectTypeName); } else { - return getImmediateFields(fieldNames, originalSelectionSets); + return getImmediateFields(fieldNames, originalSelectionSets, entityDtoObjectTypeName); } } @@ -121,9 +146,9 @@ public List getImmediateFields(@Nonnull Set fieldNames) { */ public boolean isEmpty() { if (originalSelectionSet != null) { - return isEmpty(originalSelectionSet); + return isEmpty(originalSelectionSet, entityDtoObjectTypeName); } else { - return isEmpty(originalSelectionSets); + return isEmpty(originalSelectionSets, entityDtoObjectTypeName); } } @@ -132,18 +157,29 @@ public boolean isEmpty() { * Whether there is at least one field that matches the given pattern. The pattern can be either exact field name or * field name with `*` wildcard at the end to match all fields starting with the pattern. */ - public static boolean containsImmediate(@Nonnull String fieldNamePattern, @Nonnull DataFetchingFieldSelectionSet selectionSet) { + public static boolean containsImmediate(@Nonnull String fieldNamePattern, + @Nonnull DataFetchingFieldSelectionSet selectionSet) { + return containsImmediate(fieldNamePattern, selectionSet, null); + } + + /** + * Whether there is at least one field that matches the given pattern. The pattern can be either exact field name or + * field name with `*` wildcard at the end to match all fields starting with the pattern. + */ + public static boolean containsImmediate(@Nonnull String fieldNamePattern, + @Nonnull DataFetchingFieldSelectionSet selectionSet, + @Nullable String entityDtoObjectTypeName) { final List immediateFields = selectionSet.getImmediateFields(); if (fieldNamePattern.endsWith(FIELD_NAME_PATTERN_WILDCARD)) { final String normalizedPattern = fieldNamePattern.substring(0, fieldNamePattern.length() - 1); for (SelectedField field : immediateFields) { - if (field.getName().startsWith(normalizedPattern)) { + if (isFieldOfEntityDto(field, entityDtoObjectTypeName) && field.getName().startsWith(normalizedPattern)) { return true; } } } else { for (SelectedField field : immediateFields) { - if (field.getName().equals(fieldNamePattern)) { + if (isFieldOfEntityDto(field, entityDtoObjectTypeName) && field.getName().equals(fieldNamePattern)) { return true; } } @@ -155,19 +191,30 @@ public static boolean containsImmediate(@Nonnull String fieldNamePattern, @Nonnu * Whether there is at least one field that matches the given pattern. The pattern can be either exact field name or * field name with `*` wildcard at the end to match all fields starting with the pattern. */ - public static boolean containsImmediate(@Nonnull String fieldNamePattern, @Nonnull List selectionSets) { + public static boolean containsImmediate(@Nonnull String fieldNamePattern, + @Nonnull List selectionSets) { + return containsImmediate(fieldNamePattern, selectionSets, null); + } + + /** + * Whether there is at least one field that matches the given pattern. The pattern can be either exact field name or + * field name with `*` wildcard at the end to match all fields starting with the pattern. + */ + public static boolean containsImmediate(@Nonnull String fieldNamePattern, + @Nonnull List selectionSets, + @Nullable String entityDtoObjectTypeName) { if (selectionSets.isEmpty()) { return false; } else if (selectionSets.size() == 1) { // this should not be really used, there is different variant of same method for single set, but just in case // someone forgets, this should prevent going with full list iteration - return containsImmediate(fieldNamePattern, selectionSets.get(0)); + return containsImmediate(fieldNamePattern, selectionSets.get(0), entityDtoObjectTypeName); } else { if (fieldNamePattern.endsWith(FIELD_NAME_PATTERN_WILDCARD)) { final String normalizedPattern = fieldNamePattern.substring(0, fieldNamePattern.length() - 1); for (DataFetchingFieldSelectionSet selectionSet : selectionSets) { for (SelectedField field : selectionSet.getImmediateFields()) { - if (field.getName().startsWith(normalizedPattern)) { + if (isFieldOfEntityDto(field, entityDtoObjectTypeName) && field.getName().startsWith(normalizedPattern)) { return true; } } @@ -175,7 +222,7 @@ public static boolean containsImmediate(@Nonnull String fieldNamePattern, @Nonnu } else { for (DataFetchingFieldSelectionSet selectionSet : selectionSets) { for (SelectedField field : selectionSet.getImmediateFields()) { - if (field.getName().equals(fieldNamePattern)) { + if (isFieldOfEntityDto(field, entityDtoObjectTypeName) && field.getName().equals(fieldNamePattern)) { return true; } } @@ -190,9 +237,18 @@ public static boolean containsImmediate(@Nonnull String fieldNamePattern, @Nonnu */ @Nonnull public static List getImmediateFields(@Nonnull DataFetchingFieldSelectionSet selectionSet) { + return getImmediateFields(selectionSet, null); + } + + /** + * Returns all immediate fields. + */ + @Nonnull + public static List getImmediateFields(@Nonnull DataFetchingFieldSelectionSet selectionSet, + @Nullable String entityDtoObjectTypeName) { final List matchingFields = new ArrayList<>(selectionSet.getImmediateFields().size()); for (SelectedField field : selectionSet.getImmediateFields()) { - if (!field.getName().equals(TYPENAME_FIELD)) { + if (isFieldOfEntityDto(field, entityDtoObjectTypeName) && !field.getName().equals(TYPENAME_FIELD)) { matchingFields.add(field); } } @@ -204,10 +260,19 @@ public static List getImmediateFields(@Nonnull DataFetchingFieldS */ @Nonnull public static List getImmediateFields(@Nonnull List selectionSets) { + return getImmediateFields(selectionSets, null); + } + + /** + * Returns all immediate fields. + */ + @Nonnull + public static List getImmediateFields(@Nonnull List selectionSets, + @Nullable String entityDtoObjectTypeName) { final List matchingFields = new LinkedList<>(); for (DataFetchingFieldSelectionSet selectionSet : selectionSets) { for (SelectedField field : selectionSet.getImmediateFields()) { - if (!field.getName().equals(TYPENAME_FIELD)) { + if (isFieldOfEntityDto(field, entityDtoObjectTypeName) && !field.getName().equals(TYPENAME_FIELD)) { matchingFields.add(field); } } @@ -219,10 +284,21 @@ public static List getImmediateFields(@Nonnull List getImmediateFields(@Nonnull String fieldName, @Nonnull DataFetchingFieldSelectionSet selectionSet) { + public static List getImmediateFields(@Nonnull String fieldName, + @Nonnull DataFetchingFieldSelectionSet selectionSet) { + return getImmediateFields(fieldName, selectionSet, null); + } + + /** + * Returns all immediate fields with given name. + */ + @Nonnull + public static List getImmediateFields(@Nonnull String fieldName, + @Nonnull DataFetchingFieldSelectionSet selectionSet, + @Nullable String entityDtoObjectTypeName) { final List matchingFields = new LinkedList<>(); for (SelectedField field : selectionSet.getImmediateFields()) { - if (field.getName().equals(fieldName)) { + if (isFieldOfEntityDto(field, entityDtoObjectTypeName) && field.getName().equals(fieldName)) { matchingFields.add(field); } } @@ -233,10 +309,21 @@ public static List getImmediateFields(@Nonnull String fieldName, * Returns all immediate fields with given name.s */ @Nonnull - public static List getImmediateFields(@Nonnull Set fieldNames, @Nonnull DataFetchingFieldSelectionSet selectionSet) { + public static List getImmediateFields(@Nonnull Set fieldNames, + @Nonnull DataFetchingFieldSelectionSet selectionSet) { + return getImmediateFields(fieldNames, selectionSet, null); + } + + /** + * Returns all immediate fields with given name.s + */ + @Nonnull + public static List getImmediateFields(@Nonnull Set fieldNames, + @Nonnull DataFetchingFieldSelectionSet selectionSet, + @Nullable String entityDtoObjectTypeName) { final List matchingFields = new LinkedList<>(); for (SelectedField field : selectionSet.getImmediateFields()) { - if (fieldNames.contains(field.getName())) { + if (isFieldOfEntityDto(field, entityDtoObjectTypeName) && fieldNames.contains(field.getName())) { matchingFields.add(field); } } @@ -247,19 +334,30 @@ public static List getImmediateFields(@Nonnull Set fieldN * Returns all immediate fields with given name. */ @Nonnull - public static List getImmediateFields(@Nonnull String fieldName, @Nonnull List selectionSets) { + public static List getImmediateFields(@Nonnull String fieldName, + @Nonnull List selectionSets) { + return getImmediateFields(fieldName, selectionSets, null); + } + + /** + * Returns all immediate fields with given name. + */ + @Nonnull + public static List getImmediateFields(@Nonnull String fieldName, + @Nonnull List selectionSets, + @Nullable String entityDtoObjectTypeName) { // this categorization is there just to get a little bit better performance for simple cases if (selectionSets.isEmpty()) { return EMPTY_LIST; } else if (selectionSets.size() == 1) { // this should not be really used, there is different variant of same method for single set, but just in case // someone forgets, this should prevent going with full list iteration - return getImmediateFields(fieldName, selectionSets.get(0)); + return getImmediateFields(fieldName, selectionSets.get(0), entityDtoObjectTypeName); } else { final List matchingFields = new LinkedList<>(); for (DataFetchingFieldSelectionSet selectionSet : selectionSets) { for (SelectedField field : selectionSet.getImmediateFields()) { - if (field.getName().equals(fieldName)) { + if (isFieldOfEntityDto(field, entityDtoObjectTypeName) && field.getName().equals(fieldName)) { matchingFields.add(field); } } @@ -272,19 +370,30 @@ public static List getImmediateFields(@Nonnull String fieldName, * Returns all immediate fields with given name. */ @Nonnull - public static List getImmediateFields(@Nonnull Set fieldNames, @Nonnull List selectionSets) { + public static List getImmediateFields(@Nonnull Set fieldNames, + @Nonnull List selectionSets) { + return getImmediateFields(fieldNames, selectionSets, null); + } + + /** + * Returns all immediate fields with given name. + */ + @Nonnull + public static List getImmediateFields(@Nonnull Set fieldNames, + @Nonnull List selectionSets, + @Nullable String entityDtoObjectTypeName) { // this categorization is there just to get a little bit better performance for simple cases if (selectionSets.isEmpty()) { return EMPTY_LIST; } else if (selectionSets.size() == 1) { // this should not be really used, there is different variant of same method for single set, but just in case // someone forgets, this should prevent going with full list iteration - return getImmediateFields(fieldNames, selectionSets.get(0)); + return getImmediateFields(fieldNames, selectionSets.get(0), entityDtoObjectTypeName); } else { final List matchingFields = new LinkedList<>(); for (DataFetchingFieldSelectionSet selectionSet : selectionSets) { for (SelectedField field : selectionSet.getImmediateFields()) { - if (fieldNames.contains(field.getName())) { + if (isFieldOfEntityDto(field, entityDtoObjectTypeName) && fieldNames.contains(field.getName())) { matchingFields.add(field); } } @@ -300,6 +409,18 @@ public static boolean isEmpty(@Nonnull DataFetchingFieldSelectionSet selectionSe return selectionSet.getImmediateFields().isEmpty(); } + /** + * Whether there are no fields. + */ + public static boolean isEmpty(@Nonnull DataFetchingFieldSelectionSet selectionSet, @Nullable String entityDtoObjectTypeName) { + for (SelectedField field : selectionSet.getImmediateFields()) { + if (isFieldOfEntityDto(field, entityDtoObjectTypeName)) { + return false; + } + } + return true; + } + /** * Whether there are no fields. */ @@ -320,4 +441,34 @@ public static boolean isEmpty(@Nonnull List selec } } + /** + * Whether there are no fields. + */ + public static boolean isEmpty(@Nonnull List selectionSets, @Nullable String entityDtoObjectTypeName) { + if (selectionSets.isEmpty()) { + return true; + } else if (selectionSets.size() == 1) { + // this should not be really used, there is different variant of same method for single set, but just in case + // someone forgets, this should prevent going with full list iteration + return isEmpty(selectionSets.get(0), entityDtoObjectTypeName); + } else { + for (DataFetchingFieldSelectionSet selectionSet : selectionSets) { + if (!isEmpty(selectionSet, entityDtoObjectTypeName)) { + return false; + } + } + return true; + } + } + + /** + * Whether the field is part of the entity DTO object. This is needed an interface is used and castings to implementations + * can be present and we want field only of one specific implementation. + */ + private static boolean isFieldOfEntityDto(@Nonnull SelectedField field, @Nullable String entityDtoObjectTypeName) { + if (entityDtoObjectTypeName == null) { + return true; + } + return field.getObjectTypeNames().contains(entityDtoObjectTypeName); + } } diff --git a/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/builder/GenericEntityObjectBuilder.java b/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/builder/GenericEntityObjectBuilder.java index 0b75f63d5..85437fd55 100644 --- a/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/builder/GenericEntityObjectBuilder.java +++ b/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/builder/GenericEntityObjectBuilder.java @@ -55,7 +55,7 @@ public void buildCommonTypes() { @Nonnull private OpenApiObject buildEntityObject() { - final String objectName = EntityDescriptor.THIS_GLOBAL.name(); + final String objectName = GenericEntityDescriptor.THIS.name(); // build specific entity object final OpenApiObject.Builder entityObject = GenericEntityDescriptor.THIS diff --git a/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/builder/GenericFullResponseObjectBuilder.java b/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/builder/GenericFullResponseObjectBuilder.java index b8b8e638b..46c64d1cb 100644 --- a/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/builder/GenericFullResponseObjectBuilder.java +++ b/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/builder/GenericFullResponseObjectBuilder.java @@ -128,7 +128,7 @@ private OpenApiTypeReference buildRecordPageObject() { .to(objectBuilderTransformer) .property(DataChunkDescriptor.DATA .to(propertyBuilderTransformer) - .type(nonNull(arrayOf(typeRefTo(GenericEntityDescriptor.THIS_GLOBAL.name()))))) + .type(nonNull(arrayOf(typeRefTo(GenericEntityDescriptor.THIS.name()))))) .property(DataChunkUnionDescriptor.DISCRIMINATOR .to(propertyBuilderTransformer) .type(nonNull(typeRefTo(GenericDataChunkUnionDescriptor.THIS.name())))) @@ -143,7 +143,7 @@ private OpenApiTypeReference buildRecordStripObject() { .to(objectBuilderTransformer) .property(DataChunkDescriptor.DATA .to(propertyBuilderTransformer) - .type(nonNull(arrayOf(typeRefTo(GenericEntityDescriptor.THIS_GLOBAL.name()))))) + .type(nonNull(arrayOf(typeRefTo(GenericEntityDescriptor.THIS.name()))))) .property(DataChunkUnionDescriptor.DISCRIMINATOR .to(propertyBuilderTransformer) .type(nonNull(typeRefTo(GenericDataChunkUnionDescriptor.THIS.name())))) @@ -188,7 +188,7 @@ private OpenApiTypeReference buildFacetGroupStatisticsObject() { .to(objectBuilderTransformer) .property(FacetGroupStatisticsDescriptor.GROUP_ENTITY .to(propertyBuilderTransformer) - .type(typeRefTo(GenericEntityDescriptor.THIS_GLOBAL.name()))) + .type(typeRefTo(GenericEntityDescriptor.THIS.name()))) .property(FacetGroupStatisticsDescriptor.FACET_STATISTICS .to(propertyBuilderTransformer) .type(nonNull(arrayOf(typeRefTo(GenericFacetStatisticsDescriptor.THIS.name()))))) @@ -203,7 +203,7 @@ private OpenApiTypeReference buildFacetStatisticsObject() { .to(objectBuilderTransformer) .property(FacetStatisticsDescriptor.FACET_ENTITY .to(propertyBuilderTransformer) - .type(typeRefTo(GenericEntityDescriptor.THIS_GLOBAL.name()))) + .type(typeRefTo(GenericEntityDescriptor.THIS.name()))) .build(); return buildingContext.registerType(facetStatisticsObject); @@ -225,7 +225,7 @@ private OpenApiTypeReference buildLevelInfoObject() { .to(objectBuilderTransformer) .property(LevelInfoDescriptor.ENTITY .to(propertyBuilderTransformer) - .type(nonNull(typeRefTo(GenericEntityDescriptor.THIS_GLOBAL.name())))) + .type(nonNull(typeRefTo(GenericEntityDescriptor.THIS.name())))) .property(LevelInfoDescriptor.CHILDREN .to(propertyBuilderTransformer) .type(nonNull(arrayOf(typeRefTo(GenericLevelInfoDescriptor.THIS.name()))))) diff --git a/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/model/entity/GenericEntityDescriptor.java b/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/model/entity/GenericEntityDescriptor.java index fea05ea0d..cdad49c5f 100644 --- a/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/model/entity/GenericEntityDescriptor.java +++ b/evita_external_api/evita_external_api_lab/src/main/java/io/evitadb/externalApi/lab/api/model/entity/GenericEntityDescriptor.java @@ -25,6 +25,7 @@ import io.evitadb.externalApi.api.catalog.dataApi.model.AttributesProviderDescriptor; import io.evitadb.externalApi.api.catalog.dataApi.model.EntityDescriptor; +import io.evitadb.externalApi.api.model.ObjectDescriptor; import io.evitadb.externalApi.api.model.PropertyDescriptor; import io.evitadb.externalApi.dataType.GenericObject; import io.evitadb.externalApi.rest.api.catalog.dataApi.model.entity.RestEntityDescriptor; @@ -54,4 +55,11 @@ public interface GenericEntityDescriptor extends RestEntityDescriptor { """) .type(nullable(GenericObject.class)) .build(); + + ObjectDescriptor THIS = ObjectDescriptor.extend(THIS_REFERENCE) + .name("Entity") + .description(""" + Catalog-wise entity with only common data across all entity collections. + """) + .build(); } diff --git a/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLDataEndpointFunctionalTest.java b/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLDataEndpointFunctionalTest.java index 7b850e456..2abc57c4b 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLDataEndpointFunctionalTest.java +++ b/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLDataEndpointFunctionalTest.java @@ -26,19 +26,16 @@ import io.evitadb.api.requestResponse.data.EntityClassifierWithParent; import io.evitadb.api.requestResponse.data.PriceContract; import io.evitadb.api.requestResponse.data.SealedEntity; -import io.evitadb.core.Evita; import io.evitadb.externalApi.ExternalApiFunctionTestsSupport; import io.evitadb.externalApi.api.catalog.dataApi.model.AssociatedDataDescriptor; import io.evitadb.externalApi.api.catalog.dataApi.model.EntityDescriptor; import io.evitadb.externalApi.api.catalog.dataApi.model.PriceDescriptor; import io.evitadb.externalApi.api.catalog.dataApi.model.ReferenceDescriptor; -import io.evitadb.externalApi.graphql.GraphQLProvider; +import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.GlobalEntityDescriptor; import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.GraphQLEntityDescriptor; import io.evitadb.externalApi.graphql.api.testSuite.GraphQLEndpointFunctionalTest; import io.evitadb.test.Entities; -import io.evitadb.test.annotation.DataSet; import io.evitadb.test.builder.MapBuilder; -import io.evitadb.test.extension.DataCarrier; import io.evitadb.test.tester.GraphQLTester; import io.evitadb.utils.StringUtils; @@ -49,10 +46,12 @@ import java.util.Collection; import java.util.Currency; import java.util.Deque; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; import static io.evitadb.test.TestConstants.TEST_CATALOG; import static io.evitadb.test.builder.MapBuilder.map; @@ -287,4 +286,29 @@ protected Map createEntityWithReferencedParentsDto(@Nonnull Seal .toList()) .build(); } + + @Nonnull + protected Map createTargetEntityDto(@Nonnull Map entityDto) { + return map() + .e(GlobalEntityDescriptor.TARGET_ENTITY.name(), entityDto) + .build(); + } + + @Nonnull + protected Map createTargetEntityDto(@Nonnull Map entityDto, boolean extractCommonFields) { + if (!extractCommonFields) { + return createTargetEntityDto(entityDto); + } + final MapBuilder newEntityDto = map(); + + final Map newTargetEntityDto = new HashMap<>(entityDto); + Optional.ofNullable(newTargetEntityDto.remove(EntityDescriptor.PRIMARY_KEY.name())) + .ifPresent(it -> newEntityDto.e(EntityDescriptor.PRIMARY_KEY.name(), it)); + Optional.ofNullable(newTargetEntityDto.remove(EntityDescriptor.TYPE.name())) + .ifPresent(it -> newEntityDto.e(EntityDescriptor.TYPE.name(), it)); + + return newEntityDto + .e(GlobalEntityDescriptor.TARGET_ENTITY.name(), newTargetEntityDto) + .build(); + } } diff --git a/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLGetUnknownEntityQueryFunctionalTest.java b/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLGetUnknownEntityQueryFunctionalTest.java index e106866a2..9a8c76a2c 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLGetUnknownEntityQueryFunctionalTest.java +++ b/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLGetUnknownEntityQueryFunctionalTest.java @@ -25,32 +25,39 @@ import io.evitadb.api.requestResponse.data.AttributesContract.AttributeValue; import io.evitadb.api.requestResponse.data.EntityClassifier; +import io.evitadb.api.requestResponse.data.ReferenceContract; import io.evitadb.api.requestResponse.data.SealedEntity; import io.evitadb.core.Evita; +import io.evitadb.exception.EvitaInternalError; import io.evitadb.externalApi.api.catalog.dataApi.model.AttributesDescriptor; import io.evitadb.externalApi.api.catalog.dataApi.model.EntityDescriptor; +import io.evitadb.externalApi.api.catalog.dataApi.model.PriceDescriptor; +import io.evitadb.externalApi.api.catalog.dataApi.model.ReferenceDescriptor; +import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.GlobalEntityDescriptor; import io.evitadb.test.Entities; import io.evitadb.test.annotation.UseDataSet; import io.evitadb.test.tester.GraphQLTester; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import javax.annotation.Nonnull; +import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.Objects; +import java.util.function.Predicate; import static io.evitadb.api.query.Query.query; import static io.evitadb.api.query.QueryConstraints.*; +import static io.evitadb.externalApi.graphql.api.testSuite.TestDataGenerator.ATTRIBUTE_MARKET_SHARE; import static io.evitadb.externalApi.graphql.api.testSuite.TestDataGenerator.ATTRIBUTE_RELATIVE_URL; import static io.evitadb.externalApi.graphql.api.testSuite.TestDataGenerator.GRAPHQL_THOUSAND_PRODUCTS; import static io.evitadb.test.TestConstants.TEST_CATALOG; import static io.evitadb.test.builder.MapBuilder.map; -import static io.evitadb.test.generator.DataGenerator.ATTRIBUTE_CODE; -import static io.evitadb.test.generator.DataGenerator.ATTRIBUTE_URL; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.greaterThan; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.nullValue; +import static io.evitadb.test.generator.DataGenerator.*; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Tests for GraphQL catalog unknown single entity query. @@ -94,7 +101,7 @@ void shouldReturnUnknownEntityByGloballyUniqueAttribute(Evita evita, GraphQLTest GET_ENTITY_PATH, equalTo( map() - .e(TYPENAME_FIELD, EntityDescriptor.THIS_GLOBAL.name()) + .e(TYPENAME_FIELD, GlobalEntityDescriptor.THIS.name()) .e(EntityDescriptor.PRIMARY_KEY.name(), entity.getPrimaryKey()) .e(EntityDescriptor.TYPE.name(), Entities.PRODUCT) .build() @@ -139,7 +146,7 @@ void shouldReturnUnknownEntityByGloballyUniqueLocaleSpecificAttributeWithoutSpec GET_ENTITY_PATH, equalTo( map() - .e(TYPENAME_FIELD, EntityDescriptor.THIS_GLOBAL.name()) + .e(TYPENAME_FIELD, GlobalEntityDescriptor.THIS.name()) .e(EntityDescriptor.PRIMARY_KEY.name(), entity.getPrimaryKey()) .e(EntityDescriptor.TYPE.name(), Entities.PRODUCT) .build() @@ -469,4 +476,757 @@ void shouldReturnErrorForInvalidUnknownEntityFields(GraphQLTester tester, List { + final List categories = session.queryList( + query( + collection(Entities.CATEGORY), + filterBy( + entityPrimaryKeyInSet(16) + ), + require( + entityFetch( + hierarchyContent() + ) + ) + ), + SealedEntity.class + ); + + assertEquals(1, categories.size()); + final SealedEntity c = categories.get(0); + // check that it has at least 2 parents + assertTrue(c.getParentEntity().isPresent()); + assertTrue(c.getParentEntity().get().getParentEntity().isPresent()); + return c; + } + ); + + final var expectedBody = createTargetEntityDto(createEntityWithSelfParentsDto(category, false)); + + tester.test(TEST_CATALOG) + .document(""" + { + getEntity(code: "Automotive-21") { + targetEntity { + ... on Category { + parentPrimaryKey + parents { + primaryKey + } + } + } + } + } + """) + .executeAndExpectOkAndThen() + .body(GET_ENTITY_PATH, equalTo(expectedBody)); + } + + @Test + @UseDataSet(GRAPHQL_THOUSAND_PRODUCTS) + @DisplayName("Should return direct category parent entities") + void shouldReturnTargetEntityWithAllDirectCategoryParentEntities(Evita evita, GraphQLTester tester) { + final SealedEntity category = evita.queryCatalog( + TEST_CATALOG, + session -> { + final List categories = session.queryList( + query( + collection(Entities.CATEGORY), + filterBy( + entityPrimaryKeyInSet(16) + ), + require( + entityFetch( + hierarchyContent( + entityFetch( + attributeContent(ATTRIBUTE_CODE) + ) + ) + ) + ) + ), + SealedEntity.class + ); + + assertEquals(1, categories.size()); + final SealedEntity c = categories.get(0); + // check that it has at least 2 parents + assertTrue(c.getParentEntity().isPresent()); + assertTrue(c.getParentEntity().get().getParentEntity().isPresent()); + return c; + } + ); + + final var expectedBody = createTargetEntityDto(createEntityWithSelfParentsDto(category, true)); + + tester.test(TEST_CATALOG) + .document(""" + { + getEntity(code: "Automotive-21") { + targetEntity { + ... on Category { + parentPrimaryKey + parents { + primaryKey + allLocales + attributes { + code + } + } + } + } + } + } + """) + .executeAndExpectOkAndThen() + .body(GET_ENTITY_PATH, equalTo(expectedBody)); + } + + @Test + @UseDataSet(GRAPHQL_THOUSAND_PRODUCTS) + @DisplayName("Should return only direct category parent") + void shouldReturnTargetEntityWithOnlyDirectCategoryParent(Evita evita, GraphQLTester tester) { + final SealedEntity category = evita.queryCatalog( + TEST_CATALOG, + session -> { + final List categories = session.queryList( + query( + collection(Entities.CATEGORY), + filterBy( + entityPrimaryKeyInSet(16) + ), + require( + entityFetch( + hierarchyContent( + stopAt(distance(1)) + ) + ) + ) + ), + SealedEntity.class + ); + + assertEquals(1, categories.size()); + final SealedEntity c = categories.get(0); + // check that it has only one direct parent + assertTrue(c.getParentEntity().isPresent()); + assertTrue(c.getParentEntity().get().getParentEntity().isEmpty()); + return c; + } + ); + + final var expectedBody = createTargetEntityDto(createEntityWithSelfParentsDto(category, false)); + + tester.test(TEST_CATALOG) + .document(""" + { + getEntity(code: "Automotive-21") { + targetEntity { + ... on Category { + parentPrimaryKey + parents( + stopAt: { + distance: 1 + } + ) { + primaryKey + } + } + } + } + } + """) + .executeAndExpectOkAndThen() + .body(GET_ENTITY_PATH, equalTo(expectedBody)); + } + + @Test + @UseDataSet(GRAPHQL_THOUSAND_PRODUCTS) + @DisplayName("Should return custom price for sale for single entity") + void shouldReturnTargetEntityWithCustomPriceForSaleForSingleEntity(GraphQLTester tester, List originalProductEntities) { + final SealedEntity entity = findEntityWithPrice(originalProductEntities); + + tester.test(TEST_CATALOG) + .document( + """ + query { + getEntity(code: "%s") { + primaryKey + type + targetEntity { + ... on Product { + priceForSale(currency: CZK, priceList: "basic") { + __typename + currency + priceList + priceWithTax + } + } + } + } + } + """, + entity.getAttribute(ATTRIBUTE_CODE, String.class) + ) + .executeAndThen() + .statusCode(200) + .body(ERRORS_PATH, nullValue()) + .body( + GET_ENTITY_PATH, + equalTo( + map() + .e(EntityDescriptor.PRIMARY_KEY.name(), entity.getPrimaryKey()) + .e(EntityDescriptor.TYPE.name(), Entities.PRODUCT) + .e(GlobalEntityDescriptor.TARGET_ENTITY.name(), map() + .e(EntityDescriptor.PRICE_FOR_SALE.name(), map() + .e(TYPENAME_FIELD, PriceDescriptor.THIS.name()) + .e(PriceDescriptor.CURRENCY.name(), CURRENCY_CZK.toString()) + .e(PriceDescriptor.PRICE_LIST.name(), PRICE_LIST_BASIC) + .e(PriceDescriptor.PRICE_WITH_TAX.name(), entity.getPrices(CURRENCY_CZK, PRICE_LIST_BASIC).iterator().next().priceWithTax().toString()))) + .build() + ) + ); + } + + @Test + @UseDataSet(GRAPHQL_THOUSAND_PRODUCTS) + @DisplayName("Should return formatted price for sale with custom locale") + void shouldReturnTargetEntityWithFormattedPriceForSaleWithCustomLocale(GraphQLTester tester, List originalProductEntities) { + final SealedEntity entity = findEntityWithPrice(originalProductEntities); + + tester.test(TEST_CATALOG) + .document( + """ + query { + getEntity(code: "%s") { + targetEntity { + ... on Product { + priceForSale(currency: CZK, priceList: "basic", locale: cs_CZ) { + priceWithTax(formatted: true, withCurrency: true) + } + } + } + } + } + """, + entity.getAttribute(ATTRIBUTE_CODE, String.class) + ) + .executeAndThen() + .statusCode(200) + .body(ERRORS_PATH, nullValue()) + .body(GET_ENTITY_PATH, equalTo(createTargetEntityDto(createEntityDtoWithFormattedPriceForSale(entity)))); + } + + @Test + @UseDataSet(GRAPHQL_THOUSAND_PRODUCTS) + @DisplayName("Should return error when formatting price for sale without locale") + void shouldReturnTargetEntityWithErrorWhenFormattingPriceForSaleWithoutLocale(GraphQLTester tester, List originalProductEntities) { + final SealedEntity entity = findEntityWithPrice(originalProductEntities); + + tester.test(TEST_CATALOG) + .document( + """ + query { + getEntity(code: "%s") { + targetEntity { + ... on Product { + priceForSale(currency: CZK, priceList: "basic") { + priceWithTax(formatted: true, withCurrency: true) + } + } + } + } + } + """, + entity.getAttribute(ATTRIBUTE_CODE, String.class) + ) + .executeAndThen() + .statusCode(200) + .body(ERRORS_PATH, hasSize(greaterThan(0))); + } + + @Test + @UseDataSet(GRAPHQL_THOUSAND_PRODUCTS) + @DisplayName("Should return price for single entity") + void shouldReturnTargetEntityWithPriceForSingleEntity(GraphQLTester tester, List originalProductEntities) { + final SealedEntity entity = findEntityWithPrice(originalProductEntities); + + tester.test(TEST_CATALOG) + .document( + """ + query { + getEntity(code: "%s") { + primaryKey + type + targetEntity { + ... on Product { + price(priceList: "basic", currency: CZK) { + __typename + currency + priceList + priceWithTax + } + } + } + } + } + """, + entity.getAttribute(ATTRIBUTE_CODE, String.class) + ) + .executeAndThen() + .statusCode(200) + .body(ERRORS_PATH, nullValue()) + .body(GET_ENTITY_PATH, equalTo(createTargetEntityDto(createEntityDtoWithPrice(entity), true))); + } + + @Test + @UseDataSet(GRAPHQL_THOUSAND_PRODUCTS) + @DisplayName("Should return formatted price with custom locale") + void shouldReturnTargetEntityWithFormattedPriceWithCustomLocale(GraphQLTester tester, List originalProductEntities) { + final SealedEntity entity = findEntityWithPrice(originalProductEntities); + + tester.test(TEST_CATALOG) + .document( + """ + query { + getEntity(code: "%s") { + targetEntity { + ... on Product { + price(priceList: "basic", currency: CZK, locale: cs_CZ) { + priceWithTax(formatted: true, withCurrency: true) + } + } + } + } + } + """, + entity.getAttribute(ATTRIBUTE_CODE, String.class) + ) + .executeAndThen() + .statusCode(200) + .body(ERRORS_PATH, nullValue()) + .body(GET_ENTITY_PATH, equalTo(createTargetEntityDto(createEntityDtoWithFormattedPrice(entity)))); + } + + @Test + @UseDataSet(GRAPHQL_THOUSAND_PRODUCTS) + @DisplayName("Should return error when formatting price without locale") + void shouldReturnTargetEntityWithErrorWhenFormattingPriceWithoutLocale(GraphQLTester tester, List originalProductEntities) { + final SealedEntity entity = findEntityWithPrice(originalProductEntities); + + tester.test(TEST_CATALOG) + .document( + """ + query { + getEntity(code: "%s") { + targetEntity { + ... on Product { + price(priceList: "basic", currency: CZK) { + priceWithTax(formatted: true, withCurrency: true) + } + } + } + } + } + """, + entity.getAttribute(ATTRIBUTE_CODE, String.class) + ) + .executeAndThen() + .statusCode(200) + .body(ERRORS_PATH, hasSize(greaterThan(0))); + } + + @Test + @UseDataSet(GRAPHQL_THOUSAND_PRODUCTS) + @DisplayName("Should return all prices for single entity") + void shouldReturnTargetEntityWithAllPricesForSingleEntity(GraphQLTester tester, List originalProductEntities) { + final SealedEntity entity = findEntity( + originalProductEntities, + it -> !it.getPrices().isEmpty() + ); + + tester.test(TEST_CATALOG) + .document( + """ + query { + getEntity(code: "%s") { + targetEntity { + ... on Product { + prices { + priceWithTax + } + } + } + } + } + """, + entity.getAttribute(ATTRIBUTE_CODE, String.class) + ) + .executeAndThen() + .statusCode(200) + .body(ERRORS_PATH, nullValue()) + .body( + resultPath(GET_ENTITY_PATH, GlobalEntityDescriptor.TARGET_ENTITY, EntityDescriptor.PRICES), + hasSize(greaterThan(0)) + ); + } + + @Test + @UseDataSet(GRAPHQL_THOUSAND_PRODUCTS) + @DisplayName("Should return filtered prices for single entity") + void shouldReturnTargetEntityWithFilteredPricesForSingleEntity(GraphQLTester tester, List originalProductEntities) { + final SealedEntity entity = findEntityWithPrice(originalProductEntities); + + tester.test(TEST_CATALOG) + .document( + """ + query { + getEntity(code: "%s") { + primaryKey + type + targetEntity { + ... on Product { + prices(priceLists: "basic", currency: CZK) { + __typename + currency + priceList + priceWithTax + } + } + } + } + } + """, + entity.getAttribute(ATTRIBUTE_CODE, String.class) + ) + .executeAndThen() + .statusCode(200) + .body(ERRORS_PATH, nullValue()) + .body( + GET_ENTITY_PATH, + equalTo( + map() + .e(EntityDescriptor.PRIMARY_KEY.name(), entity.getPrimaryKey()) + .e(EntityDescriptor.TYPE.name(), Entities.PRODUCT) + .e(GlobalEntityDescriptor.TARGET_ENTITY.name(), map() + .e(EntityDescriptor.PRICES.name(), List.of( + map() + .e(TYPENAME_FIELD, PriceDescriptor.THIS.name()) + .e(PriceDescriptor.CURRENCY.name(), CURRENCY_CZK.toString()) + .e(PriceDescriptor.PRICE_LIST.name(), PRICE_LIST_BASIC) + .e(PriceDescriptor.PRICE_WITH_TAX.name(), entity.getPrices(CURRENCY_CZK, PRICE_LIST_BASIC).iterator().next().priceWithTax().toString()) + .build() + ))) + .build() + ) + ); + } + + @Test + @UseDataSet(GRAPHQL_THOUSAND_PRODUCTS) + @DisplayName("Should return filtered prices for multiple price lists for single entity") + void shouldReturnTargetEntityWithFilteredPricesForMutliplePriceListsForSingleEntity(GraphQLTester tester, List originalProductEntities) { + final SealedEntity entity = findEntityWithPrice(originalProductEntities, PRICE_LIST_BASIC, PRICE_LIST_VIP); + + tester.test(TEST_CATALOG) + .document( + """ + query { + getEntity(code: "%s") { + targetEntity { + ... on Product { + prices(priceLists: ["basic", "vip"], currency: CZK) { + priceWithTax + } + } + } + } + } + """, + entity.getAttribute(ATTRIBUTE_CODE, String.class) + ) + .executeAndThen() + .statusCode(200) + .body(ERRORS_PATH, nullValue()) + .body( + GET_ENTITY_PATH, + equalTo( + map() + .e(GlobalEntityDescriptor.TARGET_ENTITY.name(), map() + .e(EntityDescriptor.PRICES.name(), List.of( + map() + .e(PriceDescriptor.PRICE_WITH_TAX.name(), entity.getPrices(CURRENCY_CZK, PRICE_LIST_BASIC).iterator().next().priceWithTax().toString()) + .build(), + map() + .e(PriceDescriptor.PRICE_WITH_TAX.name(), entity.getPrices(CURRENCY_CZK, PRICE_LIST_VIP).iterator().next().priceWithTax().toString()) + .build() + ))) + .build() + ) + ); + } + + @Test + @UseDataSet(GRAPHQL_THOUSAND_PRODUCTS) + @DisplayName("Should return formatted prices with custom locale") + void shouldReturnTargetEntityWithFormattedPricesWithCustomLocale(GraphQLTester tester, List originalProductEntities) { + final SealedEntity entity = findEntityWithPrice(originalProductEntities); + + tester.test(TEST_CATALOG) + .document( + """ + query { + getEntity(code: "%s") { + targetEntity { + ... on Product { + prices(priceLists: "basic", currency: CZK, locale: cs_CZ) { + priceWithTax(formatted: true, withCurrency: true) + } + } + } + } + } + """, + entity.getAttribute(ATTRIBUTE_CODE, String.class) + ) + .executeAndThen() + .statusCode(200) + .body(ERRORS_PATH, nullValue()) + .body(GET_ENTITY_PATH, equalTo(createTargetEntityDto(createEntityDtoWithFormattedPrices(entity)))); + } + + @Test + @UseDataSet(GRAPHQL_THOUSAND_PRODUCTS) + @DisplayName("Should return error when formatting prices without locale") + void shouldReturnTargetEntityWithErrorWhenFormattingPricesWithoutLocale(GraphQLTester tester, List originalProductEntities) { + final SealedEntity entity = findEntityWithPrice(originalProductEntities); + + tester.test(TEST_CATALOG) + .document( + """ + query { + getEntity(code: "%s") { + targetEntity { + ... on Product { + prices(priceLists: "basic", currency: CZK) { + priceWithTax(formatted: true, withCurrency: true) + } + } + } + } + } + """, + entity.getAttribute(ATTRIBUTE_CODE, String.class) + ) + .executeAndThen() + .statusCode(200) + .body(ERRORS_PATH, hasSize(greaterThan(0))); + } + + + @Test + @UseDataSet(GRAPHQL_THOUSAND_PRODUCTS) + @DisplayName("Should return associated data with custom locale for single entity") + void shouldReturnTargetEntityWithAssociatedDataWithCustomLocaleForSingleEntity(GraphQLTester tester, List originalProductEntities) { + final SealedEntity entity = findEntity( + originalProductEntities, + it -> it.getAssociatedData(ASSOCIATED_DATA_LABELS, Locale.ENGLISH) != null + ); + + tester.test(TEST_CATALOG) + .document( + """ + query { + getEntity(code: "%s") { + primaryKey + type + targetEntity { + ... on Product { + associatedData(locale: en) { + __typename + labels + } + } + } + } + } + """, + entity.getAttribute(ATTRIBUTE_CODE, String.class) + ) + .executeAndThen() + .statusCode(200) + .body(ERRORS_PATH, nullValue()) + .body(GET_ENTITY_PATH, equalTo(createTargetEntityDto(createEntityDtoWithAssociatedData(entity), true))); + } + + @Test + @UseDataSet(GRAPHQL_THOUSAND_PRODUCTS) + @DisplayName("Should return single reference for single entity") + void shouldReturnTargetEntityWithSingleReferenceForSingleEntity(Evita evita, GraphQLTester tester, List originalProductEntities) { + final SealedEntity entity = findEntity( + originalProductEntities, + it -> it.getReferences(Entities.PARAMETER).size() == 1 && + it.getReferences(Entities.PARAMETER).iterator().next().getAttribute(ATTRIBUTE_MARKET_SHARE) != null + ); + + final ReferenceContract reference = entity.getReferences(Entities.PARAMETER).iterator().next(); + final SealedEntity referencedEntity = evita.queryCatalog( + TEST_CATALOG, + session -> { + return session.getEntity(Entities.PARAMETER, reference.getReferencedPrimaryKey(), attributeContent(ATTRIBUTE_CODE)); + } + ).orElseThrow(); + final SealedEntity groupEntity = evita.queryCatalog( + TEST_CATALOG, + session -> { + return session.getEntity(Entities.PARAMETER_GROUP, reference.getGroup().get().getPrimaryKey(), attributeContent(ATTRIBUTE_CODE)); + } + ).orElseThrow(); + + tester.test(TEST_CATALOG) + .document( + """ + query { + getEntity(code: "%s") { + primaryKey + type + targetEntity { + ... on Product { + parameter { + __typename + attributes { + __typename + marketShare + } + referencedEntity { + __typename + primaryKey + type + attributes { + code + } + } + groupEntity { + __typename + primaryKey + type + attributes { + code + } + } + } + } + } + } + } + """, + entity.getAttribute(ATTRIBUTE_CODE, String.class) + ) + .executeAndThen() + .statusCode(200) + .body(ERRORS_PATH, nullValue()) + .body( + GET_ENTITY_PATH, + equalTo( + map() + .e(EntityDescriptor.PRIMARY_KEY.name(), entity.getPrimaryKey()) + .e(EntityDescriptor.TYPE.name(), Entities.PRODUCT) + .e(GlobalEntityDescriptor.TARGET_ENTITY.name(), map() + .e("parameter", map() + .e(TYPENAME_FIELD, ReferenceDescriptor.THIS.name(createEmptyEntitySchema("Product"), createEmptyEntitySchema("Parameter"))) + .e(ReferenceDescriptor.ATTRIBUTES.name(), map() + .e(TYPENAME_FIELD, AttributesDescriptor.THIS.name(createEmptyEntitySchema("Product"), createEmptyEntitySchema("Parameter"))) + .e(ATTRIBUTE_MARKET_SHARE, reference.getAttribute(ATTRIBUTE_MARKET_SHARE).toString())) + .e(ReferenceDescriptor.REFERENCED_ENTITY.name(), map() + .e(TYPENAME_FIELD, "Parameter") + .e(EntityDescriptor.PRIMARY_KEY.name(), reference.getReferencedPrimaryKey()) + .e(EntityDescriptor.TYPE.name(), reference.getReferencedEntityType()) + .e(EntityDescriptor.ATTRIBUTES.name(), map() + .e(ATTRIBUTE_CODE, referencedEntity.getAttribute(ATTRIBUTE_CODE)))) + .e(ReferenceDescriptor.GROUP_ENTITY.name(), map() + .e(TYPENAME_FIELD, "ParameterGroup") + .e(EntityDescriptor.PRIMARY_KEY.name(), reference.getGroup().get().getPrimaryKey()) + .e(EntityDescriptor.TYPE.name(), reference.getGroup().get().getType()) + .e(EntityDescriptor.ATTRIBUTES.name(), map() + .e(ATTRIBUTE_CODE, groupEntity.getAttribute(ATTRIBUTE_CODE)))))) + .build() + ) + ); + } + + @Test + @UseDataSet(GRAPHQL_THOUSAND_PRODUCTS) + @DisplayName("Should return reference list for single entity") + void shouldReturnTargetEntityWithReferenceListForSingleEntity(GraphQLTester tester, List originalProductEntities) { + final SealedEntity entity = findEntity( + originalProductEntities, + it -> it.getReferences(Entities.STORE).size() > 1 + ); + + tester.test(TEST_CATALOG) + .document( + """ + query { + getEntity(code: "%s") { + targetEntity { + ... on Product { + store { + referencedEntity { + primaryKey + } + } + } + } + } + } + """, + entity.getAttribute(ATTRIBUTE_CODE, String.class) + ) + .executeAndThen() + .statusCode(200) + .body(ERRORS_PATH, nullValue()) + .body( + resultPath(GET_ENTITY_PATH, GlobalEntityDescriptor.TARGET_ENTITY, "store", ReferenceDescriptor.REFERENCED_ENTITY, EntityDescriptor.PRIMARY_KEY), + containsInAnyOrder( + entity.getReferences(Entities.STORE) + .stream() + .map(ReferenceContract::getReferencedPrimaryKey) + .toArray(Integer[]::new) + ) + ); + } + + @Nonnull + private SealedEntity findEntity(@Nonnull List originalProductEntities, + @Nonnull Predicate filter) { + return originalProductEntities.stream() + .filter(filter) + .findFirst() + .orElseThrow(() -> new EvitaInternalError("No entity to test.")); + } + + @Nonnull + private SealedEntity findEntityWithPrice(List originalProductEntities) { + return findEntity( + originalProductEntities, + it -> it.getPrices(CURRENCY_CZK, PRICE_LIST_BASIC).size() == 1 + ); + } + + @Nonnull + private SealedEntity findEntityWithPrice(List originalProductEntities, @Nonnull String... priceLists) { + return findEntity( + originalProductEntities, + it -> Arrays.stream(priceLists).allMatch(pl -> it.getPrices(CURRENCY_CZK, pl).size() == 1) + ); + } + + } diff --git a/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLListUnknownEntitiesQueryFunctionalTest.java b/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLListUnknownEntitiesQueryFunctionalTest.java index 975c6ce8f..241020de8 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLListUnknownEntitiesQueryFunctionalTest.java +++ b/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLListUnknownEntitiesQueryFunctionalTest.java @@ -25,12 +25,16 @@ import io.evitadb.api.requestResponse.data.AttributesContract.AttributeValue; import io.evitadb.api.requestResponse.data.EntityClassifier; +import io.evitadb.api.requestResponse.data.ReferenceContract; import io.evitadb.api.requestResponse.data.SealedEntity; import io.evitadb.core.Evita; import io.evitadb.exception.EvitaInternalError; +import io.evitadb.externalApi.api.catalog.dataApi.model.AssociatedDataDescriptor; import io.evitadb.externalApi.api.catalog.dataApi.model.AttributesDescriptor; import io.evitadb.externalApi.api.catalog.dataApi.model.EntityDescriptor; -import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.GraphQLEntityDescriptor; +import io.evitadb.externalApi.api.catalog.dataApi.model.PriceDescriptor; +import io.evitadb.externalApi.api.catalog.dataApi.model.ReferenceDescriptor; +import io.evitadb.externalApi.graphql.api.catalog.dataApi.model.GlobalEntityDescriptor; import io.evitadb.test.Entities; import io.evitadb.test.annotation.UseDataSet; import io.evitadb.test.tester.GraphQLTester; @@ -38,22 +42,25 @@ import org.junit.jupiter.api.Test; import javax.annotation.Nonnull; +import java.util.Arrays; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Objects; import java.util.function.Predicate; import static io.evitadb.api.query.Query.query; import static io.evitadb.api.query.QueryConstraints.*; +import static io.evitadb.externalApi.graphql.api.testSuite.TestDataGenerator.ATTRIBUTE_MARKET_SHARE; import static io.evitadb.externalApi.graphql.api.testSuite.TestDataGenerator.ATTRIBUTE_RELATIVE_URL; import static io.evitadb.externalApi.graphql.api.testSuite.TestDataGenerator.GRAPHQL_THOUSAND_PRODUCTS; import static io.evitadb.test.TestConstants.TEST_CATALOG; import static io.evitadb.test.builder.MapBuilder.map; -import static io.evitadb.test.generator.DataGenerator.ATTRIBUTE_CODE; -import static io.evitadb.test.generator.DataGenerator.ATTRIBUTE_URL; +import static io.evitadb.test.generator.DataGenerator.*; import static org.hamcrest.Matchers.*; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Tests for GraphQL catalog unknown entity list query. @@ -101,12 +108,12 @@ void shouldReturnUnknownEntityListByMultipleGloballyUniqueAttribute(GraphQLTeste equalTo( List.of( map() - .e(TYPENAME_FIELD, GraphQLEntityDescriptor.THIS_GLOBAL.name()) + .e(TYPENAME_FIELD, GlobalEntityDescriptor.THIS.name()) .e(EntityDescriptor.PRIMARY_KEY.name(), entityWithCode1.getPrimaryKey()) .e(EntityDescriptor.TYPE.name(), Entities.PRODUCT) .build(), map() - .e(TYPENAME_FIELD, GraphQLEntityDescriptor.THIS_GLOBAL.name()) + .e(TYPENAME_FIELD, GlobalEntityDescriptor.THIS.name()) .e(EntityDescriptor.PRIMARY_KEY.name(), entityWithCode2.getPrimaryKey()) .e(EntityDescriptor.TYPE.name(), Entities.PRODUCT) .build() @@ -154,7 +161,7 @@ void shouldReturnUnknownEntityByGloballyUniqueLocaleSpecificCodeWithoutSpecifyin equalTo( List.of( map() - .e(TYPENAME_FIELD, EntityDescriptor.THIS_GLOBAL.name()) + .e(TYPENAME_FIELD, GlobalEntityDescriptor.THIS.name()) .e(EntityDescriptor.PRIMARY_KEY.name(), entity.getPrimaryKey()) .e(EntityDescriptor.TYPE.name(), Entities.PRODUCT) .build() @@ -513,6 +520,639 @@ void shouldReturnErrorForInvalidUnknownEntityListFields(GraphQLTester tester, Li .body(ERRORS_PATH, hasSize(greaterThan(0))); } + @Test + @UseDataSet(GRAPHQL_THOUSAND_PRODUCTS) + @DisplayName("Should return direct category parent entity references") + void shouldReturnAllDirectCategoryParentEntityReferences(Evita evita, GraphQLTester tester) { + final SealedEntity category = evita.queryCatalog( + TEST_CATALOG, + session -> { + final List categories = session.queryList( + query( + collection(Entities.CATEGORY), + filterBy( + entityPrimaryKeyInSet(16) + ), + require( + entityFetch( + hierarchyContent() + ) + ) + ), + SealedEntity.class + ); + + assertEquals(1, categories.size()); + final SealedEntity c = categories.get(0); + // check that it has at least 2 parents + assertTrue(c.getParentEntity().isPresent()); + assertTrue(c.getParentEntity().get().getParentEntity().isPresent()); + return c; + } + ); + + final List> expectedBody = List.of( + createTargetEntityDto(createEntityWithSelfParentsDto(category, false)) + ); + + tester.test(TEST_CATALOG) + .document(""" + { + listEntity(code: "Automotive-21") { + targetEntity { + ... on Category { + parentPrimaryKey + parents { + primaryKey + } + } + } + } + } + """) + .executeAndExpectOkAndThen() + .body(LIST_ENTITY_PATH, equalTo(expectedBody)); + } + + @Test + @UseDataSet(GRAPHQL_THOUSAND_PRODUCTS) + @DisplayName("Should return direct category parent entities") + void shouldReturnAllDirectCategoryParentEntities(Evita evita, GraphQLTester tester) { + final SealedEntity category = evita.queryCatalog( + TEST_CATALOG, + session -> { + final List categories = session.queryList( + query( + collection(Entities.CATEGORY), + filterBy( + entityPrimaryKeyInSet(16) + ), + require( + entityFetch( + hierarchyContent( + entityFetch( + attributeContent(ATTRIBUTE_CODE) + ) + ) + ) + ) + ), + SealedEntity.class + ); + + assertEquals(1, categories.size()); + final SealedEntity c = categories.get(0); + // check that it has at least 2 parents + assertTrue(c.getParentEntity().isPresent()); + assertTrue(c.getParentEntity().get().getParentEntity().isPresent()); + return c; + } + ); + + final List> expectedBody = List.of( + createTargetEntityDto(createEntityWithSelfParentsDto(category, true)) + ); + + tester.test(TEST_CATALOG) + .document(""" + { + listEntity(code: "Automotive-21") { + targetEntity { + ... on Category { + parentPrimaryKey + parents { + primaryKey + allLocales + attributes { + code + } + } + } + } + } + } + """) + .executeAndExpectOkAndThen() + .body(LIST_ENTITY_PATH, equalTo(expectedBody)); + } + + @Test + @UseDataSet(GRAPHQL_THOUSAND_PRODUCTS) + @DisplayName("Should return only direct category parent") + void shouldReturnOnlyDirectCategoryParent(Evita evita, GraphQLTester tester) { + final SealedEntity category = evita.queryCatalog( + TEST_CATALOG, + session -> { + final List categories = session.queryList( + query( + collection(Entities.CATEGORY), + filterBy( + entityPrimaryKeyInSet(16) + ), + require( + entityFetch( + hierarchyContent( + stopAt(distance(1)) + ) + ) + ) + ), + SealedEntity.class + ); + + assertEquals(1, categories.size()); + final SealedEntity c = categories.get(0); + // check that it has only one direct parent + assertTrue(c.getParentEntity().isPresent()); + assertTrue(c.getParentEntity().get().getParentEntity().isEmpty()); + return c; + } + ); + + final List> expectedBody = List.of( + createTargetEntityDto(createEntityWithSelfParentsDto(category, false)) + ); + + tester.test(TEST_CATALOG) + .document(""" + { + listEntity(code: "Automotive-21") { + targetEntity { + ... on Category { + parentPrimaryKey + parents( + stopAt: { + distance: 1 + } + ) { + primaryKey + } + } + } + } + } + """) + .executeAndExpectOkAndThen() + .body(LIST_ENTITY_PATH, equalTo(expectedBody)); + } + + @Test + @UseDataSet(GRAPHQL_THOUSAND_PRODUCTS) + @DisplayName("Should return custom price for sale for products") + void shouldReturnCustomPriceForSaleForEntities(GraphQLTester tester, List originalProductEntities) { + final List entities = findEntitiesWithPrice(originalProductEntities); + + final List> expectedBody = entities.stream() + .map(entity -> + map() + .e(EntityDescriptor.PRIMARY_KEY.name(), entity.getPrimaryKey()) + .e(EntityDescriptor.TYPE.name(), Entities.PRODUCT) + .e(GlobalEntityDescriptor.TARGET_ENTITY.name(), map() + .e(EntityDescriptor.PRICE_FOR_SALE.name(), map() + .e(TYPENAME_FIELD, PriceDescriptor.THIS.name()) + .e(PriceDescriptor.CURRENCY.name(), CURRENCY_CZK.toString()) + .e(PriceDescriptor.PRICE_LIST.name(), PRICE_LIST_BASIC) + .e(PriceDescriptor.PRICE_WITH_TAX.name(), entity.getPrices(CURRENCY_CZK, PRICE_LIST_BASIC).iterator().next().priceWithTax().toString()))) + .build() + ) + .toList(); + + tester.test(TEST_CATALOG) + .document( + """ + query { + listEntity( + code: ["%s", "%s"] + ) { + primaryKey + type + targetEntity { + ... on Product { + priceForSale(currency: CZK, priceList: "basic") { + __typename + currency + priceList + priceWithTax + } + } + } + } + } + """, + entities.get(0).getAttribute(ATTRIBUTE_CODE), + entities.get(1).getAttribute(ATTRIBUTE_CODE) + ) + .executeAndThen() + .statusCode(200) + .body(ERRORS_PATH, nullValue()) + .body(LIST_ENTITY_PATH, equalTo(expectedBody)); + } + + @Test + @UseDataSet(GRAPHQL_THOUSAND_PRODUCTS) + @DisplayName("Should return price for entities") + void shouldReturnPriceForEntities(GraphQLTester tester, List originalProductEntities) { + final List entities = findEntitiesWithPrice(originalProductEntities); + + final List> expectedBody = entities.stream() + .map(entity -> + map() + .e(EntityDescriptor.PRIMARY_KEY.name(), entity.getPrimaryKey()) + .e(EntityDescriptor.TYPE.name(), Entities.PRODUCT) + .e(GlobalEntityDescriptor.TARGET_ENTITY.name(), map() + .e(EntityDescriptor.PRICE.name(), map() + .e(TYPENAME_FIELD, PriceDescriptor.THIS.name()) + .e(PriceDescriptor.CURRENCY.name(), CURRENCY_CZK.toString()) + .e(PriceDescriptor.PRICE_LIST.name(), PRICE_LIST_BASIC) + .e(PriceDescriptor.PRICE_WITH_TAX.name(), entity.getPrices(CURRENCY_CZK, PRICE_LIST_BASIC).iterator().next().priceWithTax().toString()))) + .build() + ) + .toList(); + + tester.test(TEST_CATALOG) + .document( + """ + query { + listEntity( + code: ["%s", "%s"] + ) { + primaryKey + type + targetEntity { + ... on Product { + price(priceList: "basic", currency: CZK) { + __typename + currency + priceList + priceWithTax + } + } + } + } + } + """, + entities.get(0).getAttribute(ATTRIBUTE_CODE), + entities.get(1).getAttribute(ATTRIBUTE_CODE) + ) + .executeAndThen() + .statusCode(200) + .body(ERRORS_PATH, nullValue()) + .body(LIST_ENTITY_PATH, equalTo(expectedBody)); + } + + @Test + @UseDataSet(GRAPHQL_THOUSAND_PRODUCTS) + @DisplayName("Should return all prices for entities") + void shouldReturnAllPricesForEntities(GraphQLTester tester, List originalProductEntities) { + final List entities = findEntities( + originalProductEntities, + it -> !it.getPrices().isEmpty() + ); + + tester.test(TEST_CATALOG) + .document( + """ + query { + listEntity( + code: ["%s", "%s"] + ) { + primaryKey + type + targetEntity { + ... on Product { + prices { + priceWithTax + } + } + } + } + } + """, + entities.get(0).getAttribute(ATTRIBUTE_CODE), + entities.get(1).getAttribute(ATTRIBUTE_CODE) + ) + .executeAndThen() + .statusCode(200) + .body(ERRORS_PATH, nullValue()) + .body(LIST_ENTITY_PATH, hasSize(2)) + .body(resultPath(LIST_ENTITY_PATH + "[0]", GlobalEntityDescriptor.TARGET_ENTITY, EntityDescriptor.PRICES), hasSize(greaterThan(0))) + .body(resultPath(LIST_ENTITY_PATH + "[1]", GlobalEntityDescriptor.TARGET_ENTITY, EntityDescriptor.PRICES), hasSize(greaterThan(0))); + } + + @Test + @UseDataSet(GRAPHQL_THOUSAND_PRODUCTS) + @DisplayName("Should return filtered prices for entities") + void shouldReturnFilteredPricesForEntities(GraphQLTester tester, List originalProductEntities) { + final List entities = findEntitiesWithPrice(originalProductEntities); + + final List> expectedBody = entities.stream() + .map(entity -> + map() + .e(EntityDescriptor.PRIMARY_KEY.name(), entity.getPrimaryKey()) + .e(EntityDescriptor.TYPE.name(), Entities.PRODUCT) + .e(GlobalEntityDescriptor.TARGET_ENTITY.name(), map() + .e(EntityDescriptor.PRICES.name(), List.of( + map() + .e(TYPENAME_FIELD, PriceDescriptor.THIS.name()) + .e(PriceDescriptor.CURRENCY.name(), CURRENCY_CZK.toString()) + .e(PriceDescriptor.PRICE_LIST.name(), PRICE_LIST_BASIC) + .e(PriceDescriptor.PRICE_WITH_TAX.name(), entity.getPrices(CURRENCY_CZK, PRICE_LIST_BASIC).iterator().next().priceWithTax().toString()) + .build() + ))) + .build() + ) + .toList(); + + tester.test(TEST_CATALOG) + .document( + """ + query { + listEntity( + code: ["%s", "%s"] + ) { + primaryKey + type + targetEntity { + ... on Product { + prices(priceLists: "basic", currency: CZK) { + __typename + currency + priceList + priceWithTax + } + } + } + } + } + """, + entities.get(0).getAttribute(ATTRIBUTE_CODE), + entities.get(1).getAttribute(ATTRIBUTE_CODE) + ) + .executeAndThen() + .statusCode(200) + .body(ERRORS_PATH, nullValue()) + .body(LIST_ENTITY_PATH, equalTo(expectedBody)); + } + + @Test + @UseDataSet(GRAPHQL_THOUSAND_PRODUCTS) + @DisplayName("Should return filtered prices for multiple price lists for entities") + void shouldReturnFilteredPricesForMultiplePriceListsForEntities(GraphQLTester tester, List originalProductEntities) { + final List entities = findEntitiesWithPrice(originalProductEntities, PRICE_LIST_BASIC, PRICE_LIST_VIP); + + final List> expectedBody = entities.stream() + .map(entity -> + map() + .e(GlobalEntityDescriptor.TARGET_ENTITY.name(), map() + .e(EntityDescriptor.PRICES.name(), List.of( + map() + .e(PriceDescriptor.PRICE_WITH_TAX.name(), entity.getPrices(CURRENCY_CZK, PRICE_LIST_BASIC).iterator().next().priceWithTax().toString()) + .build(), + map() + .e(PriceDescriptor.PRICE_WITH_TAX.name(), entity.getPrices(CURRENCY_CZK, PRICE_LIST_VIP).iterator().next().priceWithTax().toString()) + .build() + ))) + .build() + ) + .toList(); + + tester.test(TEST_CATALOG) + .document( + """ + query { + listEntity( + code: ["%s", "%s"] + ) { + targetEntity { + ... on Product { + prices(priceLists: ["basic","vip"], currency: CZK) { + priceWithTax + } + } + } + } + } + """, + entities.get(0).getAttribute(ATTRIBUTE_CODE), + entities.get(1).getAttribute(ATTRIBUTE_CODE) + ) + .executeAndThen() + .statusCode(200) + .body(ERRORS_PATH, nullValue()) + .body(LIST_ENTITY_PATH, equalTo(expectedBody)); + } + + + @Test + @UseDataSet(GRAPHQL_THOUSAND_PRODUCTS) + @DisplayName("Should return associated data with custom locale for entities") + void shouldReturnAssociatedDataWithCustomLocaleForEntities(GraphQLTester tester, List originalProductEntities) { + final var entities = findEntities( + originalProductEntities, + it -> it.getAssociatedData(ASSOCIATED_DATA_LABELS, Locale.ENGLISH) != null + ); + + final var expectedBody = entities.stream() + .map(entity -> + map() + .e(EntityDescriptor.PRIMARY_KEY.name(), entity.getPrimaryKey()) + .e(EntityDescriptor.TYPE.name(), Entities.PRODUCT) + .e(GlobalEntityDescriptor.TARGET_ENTITY.name(), map() + .e(EntityDescriptor.ASSOCIATED_DATA.name(), map() + .e(TYPENAME_FIELD, AssociatedDataDescriptor.THIS.name(createEmptyEntitySchema("Product"))) + .e(ASSOCIATED_DATA_LABELS, map()))) + .build() + ) + .toList(); + + tester.test(TEST_CATALOG) + .document( + """ + query { + listEntity( + code: ["%s", "%s"] + ) { + primaryKey + type + targetEntity { + ... on Product { + associatedData(locale: en) { + __typename + labels + } + } + } + } + } + """, + entities.get(0).getAttribute(ATTRIBUTE_CODE), + entities.get(1).getAttribute(ATTRIBUTE_CODE) + ) + .executeAndThen() + .statusCode(200) + .body(ERRORS_PATH, nullValue()) + .body(LIST_ENTITY_PATH, equalTo(expectedBody)); + } + + @Test + @UseDataSet(GRAPHQL_THOUSAND_PRODUCTS) + @DisplayName("Should return single reference for entities") + void shouldReturnSingleReferenceForEntities(Evita evita, GraphQLTester tester, List originalProductEntities) { + final var entities = findEntities( + originalProductEntities, + it -> it.getReferences(Entities.PARAMETER).size() == 1 && + it.getReferences(Entities.PARAMETER).iterator().next().getAttribute(ATTRIBUTE_MARKET_SHARE) != null + ); + + final var expectedBody = entities.stream() + .map(entity -> { + final ReferenceContract reference = entity.getReferences(Entities.PARAMETER).iterator().next(); + final SealedEntity referencedEntity = evita.queryCatalog( + TEST_CATALOG, + session -> { + return session.getEntity(Entities.PARAMETER, reference.getReferencedPrimaryKey(), attributeContent(ATTRIBUTE_CODE)); + } + ).orElseThrow(); + final SealedEntity groupEntity = evita.queryCatalog( + TEST_CATALOG, + session -> { + return session.getEntity(Entities.PARAMETER_GROUP, reference.getGroup().get().getPrimaryKey(), attributeContent(ATTRIBUTE_CODE)); + } + ).orElseThrow(); + + return map() + .e(EntityDescriptor.PRIMARY_KEY.name(), entity.getPrimaryKey()) + .e(EntityDescriptor.TYPE.name(), Entities.PRODUCT) + .e(GlobalEntityDescriptor.TARGET_ENTITY.name(), map() + .e("parameter", map() + .e(TYPENAME_FIELD, ReferenceDescriptor.THIS.name(createEmptyEntitySchema("Product"), createEmptyEntitySchema("Parameter"))) + .e(ReferenceDescriptor.ATTRIBUTES.name(), map() + .e(TYPENAME_FIELD, AttributesDescriptor.THIS.name(createEmptyEntitySchema("Product"), createEmptyEntitySchema("Parameter"))) + .e(ATTRIBUTE_MARKET_SHARE, reference.getAttribute(ATTRIBUTE_MARKET_SHARE).toString())) + .e(ReferenceDescriptor.REFERENCED_ENTITY.name(), map() + .e(TYPENAME_FIELD, "Parameter") + .e(EntityDescriptor.PRIMARY_KEY.name(), reference.getReferencedPrimaryKey()) + .e(EntityDescriptor.TYPE.name(), reference.getReferencedEntityType()) + .e(EntityDescriptor.ATTRIBUTES.name(), map() + .e(ATTRIBUTE_CODE, referencedEntity.getAttribute(ATTRIBUTE_CODE)))) + .e(ReferenceDescriptor.GROUP_ENTITY.name(), map() + .e(TYPENAME_FIELD, "ParameterGroup") + .e(EntityDescriptor.PRIMARY_KEY.name(), reference.getGroup().get().getPrimaryKey()) + .e(EntityDescriptor.TYPE.name(), reference.getGroup().get().getType()) + .e(EntityDescriptor.ATTRIBUTES.name(), map() + .e(ATTRIBUTE_CODE, groupEntity.getAttribute(ATTRIBUTE_CODE)))))) + .build(); + }) + .toList(); + + tester.test(TEST_CATALOG) + .document( + """ + query { + listEntity( + code: ["%s", "%s"] + ) { + primaryKey + type + targetEntity { + ... on Product { + parameter { + __typename + attributes { + __typename + marketShare + } + referencedEntity { + __typename + primaryKey + type + attributes { + code + } + } + groupEntity { + __typename + primaryKey + type + attributes { + code + } + } + } + } + } + } + } + """, + entities.get(0).getAttribute(ATTRIBUTE_CODE), + entities.get(1).getAttribute(ATTRIBUTE_CODE) + ) + .executeAndThen() + .statusCode(200) + .body(ERRORS_PATH, nullValue()) + .body(LIST_ENTITY_PATH, equalTo(expectedBody)); + } + + @Test + @UseDataSet(GRAPHQL_THOUSAND_PRODUCTS) + @DisplayName("Should return reference list for entities") + void shouldReturnReferenceListForEntities(GraphQLTester tester, List originalProductEntities) { + final var entities = findEntities( + originalProductEntities, + it -> it.getReferences(Entities.STORE).size() > 1 + ); + + final var expectedBody = entities.stream() + .map(entity -> { + final var references = entity.getReferences(Entities.STORE) + .stream() + .map(reference -> + map() + .e(ReferenceDescriptor.REFERENCED_ENTITY.name(), map() + .e(EntityDescriptor.PRIMARY_KEY.name(), reference.getReferencedPrimaryKey()) + .build()) + .build()) + .toList(); + + return map() + .e(EntityDescriptor.PRIMARY_KEY.name(), entity.getPrimaryKey()) + .e(EntityDescriptor.TYPE.name(), Entities.PRODUCT) + .e(GlobalEntityDescriptor.TARGET_ENTITY.name(), map() + .e("store", references)) + .build(); + }) + .toList(); + + tester.test(TEST_CATALOG) + .document( + """ + query { + listEntity( + code: ["%s", "%s"] + ) { + primaryKey + type + targetEntity { + ... on Product { + store { + referencedEntity { + primaryKey + } + } + } + } + } + } + """, + entities.get(0).getAttribute(ATTRIBUTE_CODE), + entities.get(1).getAttribute(ATTRIBUTE_CODE) + ) + .executeAndThen() + .statusCode(200) + .body(ERRORS_PATH, nullValue()) + .body(LIST_ENTITY_PATH, equalTo(expectedBody)); + } + @Nonnull private List findEntities(@Nonnull List originalProductEntities, @@ -524,4 +1164,22 @@ private List findEntities(@Nonnull List originalProd assertEquals(2, entities.size()); return entities; } + + @Nonnull + private List findEntitiesWithPrice(List originalProductEntities) { + return findEntities( + originalProductEntities, + it -> it.getPrices(CURRENCY_CZK, "basic").size() == 1 + ); + } + + + @Nonnull + private List findEntitiesWithPrice(List originalProductEntities, @Nonnull String... priceLists) { + return findEntities( + originalProductEntities, + it -> Arrays.stream(priceLists).allMatch(pl -> it.getPrices(CURRENCY_CZK, pl).size() == 1) + ); + } } + From 948387073c1b5611ac1ff7b382d3972102df5309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Fri, 15 Dec 2023 16:19:22 +0100 Subject: [PATCH 48/52] refactor(#373): refactor tests --- ...alogGraphQLDataEndpointFunctionalTest.java | 16 ++++++ ...ogGraphQLGetEntityQueryFunctionalTest.java | 15 +----- ...QLGetUnknownEntityQueryFunctionalTest.java | 29 +--------- ...raphQLListEntitiesQueryFunctionalTest.java | 15 +----- ...istUnknownEntitiesQueryFunctionalTest.java | 53 ++----------------- ...GraphQLQueryEntityQueryFunctionalTest.java | 17 +----- 6 files changed, 25 insertions(+), 120 deletions(-) diff --git a/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLDataEndpointFunctionalTest.java b/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLDataEndpointFunctionalTest.java index 2abc57c4b..669642334 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLDataEndpointFunctionalTest.java +++ b/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLDataEndpointFunctionalTest.java @@ -221,6 +221,22 @@ protected Map createEntityDtoWithPrice(@Nonnull SealedEntity ent .build(); } + @Nonnull + protected Map createEntityDtoWithPrices(@Nonnull SealedEntity entity) { + return map() + .e(EntityDescriptor.PRIMARY_KEY.name(), entity.getPrimaryKey()) + .e(EntityDescriptor.TYPE.name(), Entities.PRODUCT) + .e(EntityDescriptor.PRICES.name(), List.of( + map() + .e(TYPENAME_FIELD, PriceDescriptor.THIS.name()) + .e(PriceDescriptor.CURRENCY.name(), CURRENCY_CZK.toString()) + .e(PriceDescriptor.PRICE_LIST.name(), PRICE_LIST_BASIC) + .e(PriceDescriptor.PRICE_WITH_TAX.name(), entity.getPrices(CURRENCY_CZK, PRICE_LIST_BASIC).iterator().next().priceWithTax().toString()) + .build() + )) + .build(); + } + @Nonnull protected Map createEntityDtoWithAssociatedData(@Nonnull SealedEntity entity) { return map() diff --git a/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLGetEntityQueryFunctionalTest.java b/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLGetEntityQueryFunctionalTest.java index bd8a569a0..2799f2531 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLGetEntityQueryFunctionalTest.java +++ b/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLGetEntityQueryFunctionalTest.java @@ -1191,20 +1191,7 @@ void shouldReturnFilteredPricesForSingleProduct(GraphQLTester tester, List entities = findEntitiesWithPrice(originalProductEntities); final List> expectedBody = entities.stream() - .map(entity -> - map() - .e(EntityDescriptor.PRIMARY_KEY.name(), entity.getPrimaryKey()) - .e(EntityDescriptor.TYPE.name(), Entities.PRODUCT) - .e(EntityDescriptor.PRICES.name(), List.of( - map() - .e(TYPENAME_FIELD, PriceDescriptor.THIS.name()) - .e(PriceDescriptor.CURRENCY.name(), CURRENCY_CZK.toString()) - .e(PriceDescriptor.PRICE_LIST.name(), PRICE_LIST_BASIC) - .e(PriceDescriptor.PRICE_WITH_TAX.name(), entity.getPrices(CURRENCY_CZK, PRICE_LIST_BASIC).iterator().next().priceWithTax().toString()) - .build() - )) - .build() - ) + .map(this::createEntityDtoWithPrices) .toList(); tester.test(TEST_CATALOG) diff --git a/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLListUnknownEntitiesQueryFunctionalTest.java b/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLListUnknownEntitiesQueryFunctionalTest.java index 241020de8..f12788586 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLListUnknownEntitiesQueryFunctionalTest.java +++ b/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLListUnknownEntitiesQueryFunctionalTest.java @@ -703,18 +703,7 @@ void shouldReturnCustomPriceForSaleForEntities(GraphQLTester tester, List entities = findEntitiesWithPrice(originalProductEntities); final List> expectedBody = entities.stream() - .map(entity -> - map() - .e(EntityDescriptor.PRIMARY_KEY.name(), entity.getPrimaryKey()) - .e(EntityDescriptor.TYPE.name(), Entities.PRODUCT) - .e(GlobalEntityDescriptor.TARGET_ENTITY.name(), map() - .e(EntityDescriptor.PRICE_FOR_SALE.name(), map() - .e(TYPENAME_FIELD, PriceDescriptor.THIS.name()) - .e(PriceDescriptor.CURRENCY.name(), CURRENCY_CZK.toString()) - .e(PriceDescriptor.PRICE_LIST.name(), PRICE_LIST_BASIC) - .e(PriceDescriptor.PRICE_WITH_TAX.name(), entity.getPrices(CURRENCY_CZK, PRICE_LIST_BASIC).iterator().next().priceWithTax().toString()))) - .build() - ) + .map(entity -> createTargetEntityDto(createEntityDtoWithPriceForSale(entity), true)) .toList(); tester.test(TEST_CATALOG) @@ -755,18 +744,7 @@ void shouldReturnPriceForEntities(GraphQLTester tester, List origi final List entities = findEntitiesWithPrice(originalProductEntities); final List> expectedBody = entities.stream() - .map(entity -> - map() - .e(EntityDescriptor.PRIMARY_KEY.name(), entity.getPrimaryKey()) - .e(EntityDescriptor.TYPE.name(), Entities.PRODUCT) - .e(GlobalEntityDescriptor.TARGET_ENTITY.name(), map() - .e(EntityDescriptor.PRICE.name(), map() - .e(TYPENAME_FIELD, PriceDescriptor.THIS.name()) - .e(PriceDescriptor.CURRENCY.name(), CURRENCY_CZK.toString()) - .e(PriceDescriptor.PRICE_LIST.name(), PRICE_LIST_BASIC) - .e(PriceDescriptor.PRICE_WITH_TAX.name(), entity.getPrices(CURRENCY_CZK, PRICE_LIST_BASIC).iterator().next().priceWithTax().toString()))) - .build() - ) + .map(entity -> createTargetEntityDto(createEntityDtoWithPrice(entity, CURRENCY_CZK, PRICE_LIST_BASIC), true)) .toList(); tester.test(TEST_CATALOG) @@ -846,21 +824,7 @@ void shouldReturnFilteredPricesForEntities(GraphQLTester tester, List entities = findEntitiesWithPrice(originalProductEntities); final List> expectedBody = entities.stream() - .map(entity -> - map() - .e(EntityDescriptor.PRIMARY_KEY.name(), entity.getPrimaryKey()) - .e(EntityDescriptor.TYPE.name(), Entities.PRODUCT) - .e(GlobalEntityDescriptor.TARGET_ENTITY.name(), map() - .e(EntityDescriptor.PRICES.name(), List.of( - map() - .e(TYPENAME_FIELD, PriceDescriptor.THIS.name()) - .e(PriceDescriptor.CURRENCY.name(), CURRENCY_CZK.toString()) - .e(PriceDescriptor.PRICE_LIST.name(), PRICE_LIST_BASIC) - .e(PriceDescriptor.PRICE_WITH_TAX.name(), entity.getPrices(CURRENCY_CZK, PRICE_LIST_BASIC).iterator().next().priceWithTax().toString()) - .build() - ))) - .build() - ) + .map(entity -> createTargetEntityDto(createEntityDtoWithPrices(entity), true)) .toList(); tester.test(TEST_CATALOG) @@ -953,16 +917,7 @@ void shouldReturnAssociatedDataWithCustomLocaleForEntities(GraphQLTester tester, ); final var expectedBody = entities.stream() - .map(entity -> - map() - .e(EntityDescriptor.PRIMARY_KEY.name(), entity.getPrimaryKey()) - .e(EntityDescriptor.TYPE.name(), Entities.PRODUCT) - .e(GlobalEntityDescriptor.TARGET_ENTITY.name(), map() - .e(EntityDescriptor.ASSOCIATED_DATA.name(), map() - .e(TYPENAME_FIELD, AssociatedDataDescriptor.THIS.name(createEmptyEntitySchema("Product"))) - .e(ASSOCIATED_DATA_LABELS, map()))) - .build() - ) + .map(entity -> createTargetEntityDto(createEntityDtoWithAssociatedData(entity), true)) .toList(); tester.test(TEST_CATALOG) diff --git a/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLQueryEntityQueryFunctionalTest.java b/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLQueryEntityQueryFunctionalTest.java index 3c333f2f2..ee5215c6d 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLQueryEntityQueryFunctionalTest.java +++ b/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLQueryEntityQueryFunctionalTest.java @@ -1772,22 +1772,7 @@ void shouldReturnAllPricesForProducts(GraphQLTester tester, List o void shouldReturnFilteredPricesForProducts(GraphQLTester tester, List originalProductEntities) { final List entities = findEntitiesWithPrice(originalProductEntities, 2); - final var expectedBody = createBasicPageResponse( - entities, - entity -> - map() - .e(EntityDescriptor.PRIMARY_KEY.name(), entity.getPrimaryKey()) - .e(EntityDescriptor.TYPE.name(), Entities.PRODUCT) - .e("prices", List.of( - map() - .e(TYPENAME_FIELD, PriceDescriptor.THIS.name()) - .e(PriceDescriptor.CURRENCY.name(), CURRENCY_CZK.toString()) - .e(PriceDescriptor.PRICE_LIST.name(), PRICE_LIST_BASIC) - .e(PriceDescriptor.PRICE_WITH_TAX.name(), entity.getPrices(CURRENCY_CZK, PRICE_LIST_BASIC).iterator().next().priceWithTax().toString()) - .build() - )) - .build() - ); + final var expectedBody = createBasicPageResponse(entities, this::createEntityDtoWithPrices); tester.test(TEST_CATALOG) .document( From 42bf3a2fb4708f025c3c3e2fb8b3252bff624729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Fri, 15 Dec 2023 16:29:43 +0100 Subject: [PATCH 49/52] fix: locale argument in generic entity query is not passed into the resolve context --- .../resolver/dataFetcher/GetUnknownEntityDataFetcher.java | 7 ++++++- .../dataFetcher/ListUnknownEntitiesDataFetcher.java | 7 ++++++- .../CatalogGraphQLGetUnknownEntityQueryFunctionalTest.java | 6 ++++++ ...talogGraphQLListUnknownEntitiesQueryFunctionalTest.java | 6 ++++++ 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/resolver/dataFetcher/GetUnknownEntityDataFetcher.java b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/resolver/dataFetcher/GetUnknownEntityDataFetcher.java index 819f321c9..ea1c45c3f 100644 --- a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/resolver/dataFetcher/GetUnknownEntityDataFetcher.java +++ b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/resolver/dataFetcher/GetUnknownEntityDataFetcher.java @@ -165,7 +165,7 @@ public DataFetcherResult get(@Nonnull DataFetchingEnvironment envi if (loadedEntity != null) { resultBuilder .data(loadedEntity) - .localContext(EntityQueryContext.empty()); + .localContext(buildResultContext(arguments)); } return resultBuilder.build(); @@ -232,6 +232,11 @@ private Optional buildEnrichingRequires(@Nonnull DataFet ); } + @Nonnull + private static EntityQueryContext buildResultContext(@Nonnull Arguments arguments) { + return new EntityQueryContext(arguments.locale(), null, null, null, false); + } + /** * Holds parsed GraphQL query arguments relevant for single entity query */ diff --git a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/resolver/dataFetcher/ListUnknownEntitiesDataFetcher.java b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/resolver/dataFetcher/ListUnknownEntitiesDataFetcher.java index fd1532f0d..5122879ec 100644 --- a/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/resolver/dataFetcher/ListUnknownEntitiesDataFetcher.java +++ b/evita_external_api/evita_external_api_graphql/src/main/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/resolver/dataFetcher/ListUnknownEntitiesDataFetcher.java @@ -175,7 +175,7 @@ public DataFetcherResult> get(@Nonnull DataFetchingEnvironmen final DataFetcherResult.Builder> resultBuilder = DataFetcherResult.>newResult() .data(loadedEntities); if (!entities.isEmpty()) { - resultBuilder.localContext(EntityQueryContext.empty()); + resultBuilder.localContext(buildResultContext(arguments)); } return resultBuilder.build(); @@ -255,6 +255,11 @@ private Optional buildEnrichingRequires(@Nonnull DataFet ); } + @Nonnull + private static EntityQueryContext buildResultContext(@Nonnull Arguments arguments) { + return new EntityQueryContext(arguments.locale(), null, null, null, false); + } + /** * Holds parsed GraphQL query arguments relevant for single entity query */ diff --git a/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLGetUnknownEntityQueryFunctionalTest.java b/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLGetUnknownEntityQueryFunctionalTest.java index af7e28795..de1175561 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLGetUnknownEntityQueryFunctionalTest.java +++ b/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLGetUnknownEntityQueryFunctionalTest.java @@ -133,6 +133,9 @@ void shouldReturnUnknownEntityByGloballyUniqueLocaleSpecificAttributeWithoutSpec __typename primaryKey type + attributes { + relativeUrl + } } } """, @@ -149,6 +152,9 @@ void shouldReturnUnknownEntityByGloballyUniqueLocaleSpecificAttributeWithoutSpec .e(TYPENAME_FIELD, GlobalEntityDescriptor.THIS.name()) .e(EntityDescriptor.PRIMARY_KEY.name(), entity.getPrimaryKey()) .e(EntityDescriptor.TYPE.name(), Entities.PRODUCT) + .e(EntityDescriptor.ATTRIBUTES.name(), map() + .e(ATTRIBUTE_RELATIVE_URL, relativeUrl.value()) + .build()) .build() ) ); diff --git a/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLListUnknownEntitiesQueryFunctionalTest.java b/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLListUnknownEntitiesQueryFunctionalTest.java index f12788586..fd2fa91b8 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLListUnknownEntitiesQueryFunctionalTest.java +++ b/evita_functional_tests/src/test/java/io/evitadb/externalApi/graphql/api/catalog/dataApi/CatalogGraphQLListUnknownEntitiesQueryFunctionalTest.java @@ -147,6 +147,9 @@ void shouldReturnUnknownEntityByGloballyUniqueLocaleSpecificCodeWithoutSpecifyin __typename primaryKey type + attributes { + relativeUrl + } } } """, @@ -164,6 +167,9 @@ void shouldReturnUnknownEntityByGloballyUniqueLocaleSpecificCodeWithoutSpecifyin .e(TYPENAME_FIELD, GlobalEntityDescriptor.THIS.name()) .e(EntityDescriptor.PRIMARY_KEY.name(), entity.getPrimaryKey()) .e(EntityDescriptor.TYPE.name(), Entities.PRODUCT) + .e(EntityDescriptor.ATTRIBUTES.name(), map() + .e(ATTRIBUTE_RELATIVE_URL, relativeUrl.value()) + .build()) .build() ) ) From 5289bad5a7e4badd86a16fc116edc2aeaae2dd8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Sun, 17 Dec 2023 13:12:55 +0100 Subject: [PATCH 50/52] fix: cannot parse generic query (without defined collection) --- .../parser/visitor/EvitaQLQueryVisitorTest.java | 7 +++++++ .../query/parser/visitor/EvitaQLQueryVisitor.java | 14 +++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/evita_functional_tests/src/test/java/io/evitadb/api/query/parser/visitor/EvitaQLQueryVisitorTest.java b/evita_functional_tests/src/test/java/io/evitadb/api/query/parser/visitor/EvitaQLQueryVisitorTest.java index 246c7c607..2f6300ec3 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/api/query/parser/visitor/EvitaQLQueryVisitorTest.java +++ b/evita_functional_tests/src/test/java/io/evitadb/api/query/parser/visitor/EvitaQLQueryVisitorTest.java @@ -49,6 +49,13 @@ class EvitaQLQueryVisitorTest { @Test void shouldParseQuery() { + assertEquals( + query( + filterBy(attributeEquals("a", true)) + ), + parseQuery("query(filterBy(attributeEqualsTrue('a')))") + ); + assertEquals( query( collection("a") 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 848a89ea2..79dc232b3 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 @@ -39,6 +39,7 @@ import org.antlr.v4.runtime.ParserRuleContext; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.List; import java.util.stream.Collectors; @@ -64,7 +65,7 @@ public Query visitQuery(@Nonnull EvitaQLParser.QueryContext ctx) { .map(con -> con.accept(constraintVisitor)) .collect(Collectors.toList()); - final Collection collectionConstraint = findEntitiesConstraint(ctx, constraints); + final Collection collectionConstraint = findCollectionConstraint(ctx, constraints); final FilterBy filterByConstraint = findFilterByConstraint(ctx, constraints); final OrderBy orderByConstraint = findOrderByConstraint(ctx, constraints); final Require requireConstraint = findRequireConstraint(ctx, constraints); @@ -86,12 +87,16 @@ public Query visitQuery(@Nonnull EvitaQLParser.QueryContext ctx) { * @return found {@link Collection} * @throws EvitaQLInvalidQueryError if no appropriated constraint found */ - protected Collection findEntitiesConstraint(@Nonnull ParserRuleContext ctx, @Nonnull List> topLevelConstraints) { + @Nullable + protected Collection findCollectionConstraint(@Nonnull ParserRuleContext ctx, @Nonnull List> topLevelConstraints) { final List> headConstraints = topLevelConstraints.stream() .filter(HeadConstraint.class::isInstance) .toList(); - if ((headConstraints.size() != 1) || !(headConstraints.get(0) instanceof Collection)) { + if (headConstraints.isEmpty()) { + return null; + } + if ((headConstraints.size() > 1) || !(headConstraints.get(0) instanceof Collection)) { throw new EvitaQLInvalidQueryError( ctx, "Query can have only one top level head constraint -> `collection`." @@ -107,6 +112,7 @@ protected Collection findEntitiesConstraint(@Nonnull ParserRuleContext ctx, @Non * @return {@code null} if not appropriated constraint found or found {@link FilterBy} * @throws EvitaQLInvalidQueryError if there is more top level filter constraints or found constraint is not appropriated type */ + @Nullable protected FilterBy findFilterByConstraint(@Nonnull ParserRuleContext ctx, @Nonnull List> topLevelConstraints) { final List> filterConstraints = topLevelConstraints.stream() .filter(FilterConstraint.class::isInstance) @@ -131,6 +137,7 @@ protected FilterBy findFilterByConstraint(@Nonnull ParserRuleContext ctx, @Nonnu * @return {@code null} if not appropriated constraint found or found {@link OrderBy} * @throws EvitaQLInvalidQueryError if there is more top level order constraints or found constraint is not appropriated type */ + @Nullable protected OrderBy findOrderByConstraint(@Nonnull ParserRuleContext ctx, @Nonnull List> topLevelConstraints) { final List> orderConstraints = topLevelConstraints.stream() .filter(OrderConstraint.class::isInstance) @@ -155,6 +162,7 @@ protected OrderBy findOrderByConstraint(@Nonnull ParserRuleContext ctx, @Nonnull * @return {@code null} if not appropriated constraint found or found {@link Require} * @throws EvitaQLInvalidQueryError if there is more top level require constraints or found constraint is not appropriated type */ + @Nullable protected Require findRequireConstraint(@Nonnull ParserRuleContext ctx, @Nonnull List> topLevelConstraints) { final List> requireConstraints = topLevelConstraints.stream() .filter(RequireConstraint.class::isInstance) From f378b2fb2dcc9a93462b9561726e2ebee579ff84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Mon, 18 Dec 2023 08:49:33 +0100 Subject: [PATCH 51/52] feat: export type of constraint --- .../test/java/io/evitadb/documentation/JavaDocCopy.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/evita_functional_tests/src/test/java/io/evitadb/documentation/JavaDocCopy.java b/evita_functional_tests/src/test/java/io/evitadb/documentation/JavaDocCopy.java index 8b53e6ef1..989a58b51 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/documentation/JavaDocCopy.java +++ b/evita_functional_tests/src/test/java/io/evitadb/documentation/JavaDocCopy.java @@ -317,10 +317,18 @@ void exportConstraintDefinitions() throws URISyntaxException, IOException { } else { constraintName = StringUtils.uncapitalize(constraintClass.getName()); } + final String type = switch (constraintClass.getPackageName()) { + case "io.evitadb.api.query.head" -> "HEAD"; + case "io.evitadb.api.query.filter" -> "FILTER"; + case "io.evitadb.api.query.order" -> "ORDER"; + case "io.evitadb.api.query.require" -> "REQUIRE"; + default -> throw new EvitaInternalError("Unknown package name: " + constraintClass.getPackageName()); + }; final String shortDescription = ((String) constraintDefinition.get().getNamedParameter("shortDescription")).replace("\"", ""); final String userDocsLink = "https://evitadb.io" + ((String) constraintDefinition.get().getNamedParameter("userDocsLink")).replace("\"", ""); final ObjectNode exportedConstraintDefinition = objectMapper.createObjectNode(); + exportedConstraintDefinition.put("type", type); exportedConstraintDefinition.put("shortDescription", shortDescription); exportedConstraintDefinition.put("userDocsLink", userDocsLink); export.putIfAbsent(constraintName, exportedConstraintDefinition); From 2c60aa789a08a34f5d2d092bd6ddb2bcc6503969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hornych?= Date: Mon, 18 Dec 2023 09:01:48 +0100 Subject: [PATCH 52/52] fix: invalid query test --- .../api/query/parser/visitor/EvitaQLQueryVisitorTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/evita_functional_tests/src/test/java/io/evitadb/api/query/parser/visitor/EvitaQLQueryVisitorTest.java b/evita_functional_tests/src/test/java/io/evitadb/api/query/parser/visitor/EvitaQLQueryVisitorTest.java index 2f6300ec3..5713fd7a2 100644 --- a/evita_functional_tests/src/test/java/io/evitadb/api/query/parser/visitor/EvitaQLQueryVisitorTest.java +++ b/evita_functional_tests/src/test/java/io/evitadb/api/query/parser/visitor/EvitaQLQueryVisitorTest.java @@ -261,7 +261,6 @@ void shouldNotParseQuery() { assertThrows(EvitaQLInvalidQueryError.class, () -> parseQuery("query(require(attributeContentAll()),collection('a'),orderBy(attributeNatural('c')),filterBy(attributeEquals('a',1)))")); assertThrows(EvitaQLInvalidQueryError.class, () -> parseQueryUnsafe("query")); assertThrows(EvitaQLInvalidQueryError.class, () -> parseQueryUnsafe("query()")); - assertThrows(EvitaQLInvalidQueryError.class, () -> parseQueryUnsafe("query(filterBy(attributeEquals('a',1)))")); assertThrows(EvitaQLInvalidQueryError.class, () -> parseQueryUnsafe("query(collection('a'),attributeEquals('b',1))")); assertThrows(EvitaQLInvalidQueryError.class, () -> parseQueryUnsafe("query(collection('a'),attributeContent('b'))")); assertThrows(EvitaQLInvalidQueryError.class, () -> parseQueryUnsafe("query(collection('a'),attributeContentAll())"));