Skip to content

Commit 40354a7

Browse files
BalduinLandoltirinaschubert
andauthored
feat(gravsearch): improve gravsearch performance by using unions in prequery (DEV-492) (#2045)
* Update SparqlTransformer.scala * feat: add superPropertyOf map to ontology cache * refactor: reduce logging noise * chore: add clean-sbt target to makefile * feat: replace property path query statements with unions for subPropertyOf* * feat: use unions for subclasses * refactor: tidy up some old mess * refactor: add more logging * add limiting param to transformer to reduce inference * ignore failing test for now * feat: start working on reducing union options on basis of the query * tidy up * minor improvements * get tests to pass * feat: limit subclasses * feat: include optimization in count query * test: minimal test for compound objects with gravsearch * test: test simulated inference with union patterns * refactor start tidying up * refactor: more tidying up * refactor: tidy up * docs: start documenting the changes * refactor: remove unused code * docs: update documentation * refactor: remove some code smells * refactor: tidy up, improve variable naming and add documentation * refactor: format sparqlTransformarSpec.scala * Apply suggestions from code review Co-authored-by: irinaschubert <irina.schubert@dasch.swiss> * tidy up * wrap up according to review Co-authored-by: irinaschubert <irina.schubert@dasch.swiss>
1 parent a9fda7e commit 40354a7

21 files changed

Lines changed: 985 additions & 527 deletions

‎Makefile‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,13 @@ clean-local-tmp:
280280
@rm -rf .tmp
281281
@mkdir .tmp
282282

283+
.PHONY: clean-metals
284+
clean-metals: ## clean SBT and Metals related stuff
285+
@rm -rf .bloop
286+
@rm -rf .bsp
287+
@rm -rf .metals
288+
@rm -rf target
289+
283290
clean: docs-clean clean-local-tmp clean-docker clean-sipi-tmp ## clean build artifacts
284291
@rm -rf .env
285292

‎docs/01-introduction/what-is-knora.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ and can regenerate the original XML document at any time.
7474

7575
DSP-API provides a search language, [Gravsearch](../03-apis/api-v2/query-language.md),
7676
that is designed to meet the needs of humanities researchers. Gravsearch supports DSP-API's
77-
humanites-focused data structures, including calendar-independent dates and standoff markup, as well
77+
humanities-focused data structures, including calendar-independent dates and standoff markup, as well
7878
as fast full-text searches. This allows searches to combine text-related criteria with any other
7979
criteria. For example, you could search for a text that contains a certain word
8080
and also mentions a person who lived in the same city as another person who is the

‎docs/03-apis/api-v2/query-language.md‎

Lines changed: 23 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ criteria) while avoiding their drawbacks in terms of performance and
1313
security (see [The Enduring Myth of the SPARQL
1414
Endpoint](https://daverog.wordpress.com/2013/06/04/the-enduring-myth-of-the-sparql-endpoint/)).
1515
It also has the benefit of enabling clients to work with a simpler RDF
16-
data model than the one Knora actually uses to store data in the
16+
data model than the one the API actually uses to store data in the
1717
triplestore, and makes it possible to provide better error-checking.
1818

1919
Rather than being processed directly by the triplestore, a Gravsearch query
20-
is interpreted by Knora, which enforces certain
20+
is interpreted by the API, which enforces certain
2121
restrictions on the query, and implements paging and permission
2222
checking. The API server generates SPARQL based on the Gravsearch query
2323
submitted, queries the triplestore, filters the results according to the
24-
user's permissions, and returns each page of query results as a Knora
24+
user's permissions, and returns each page of query results as an
2525
API response. Thus, Gravsearch is a hybrid between a RESTful API and a
2626
SPARQL endpoint.
2727

@@ -80,14 +80,14 @@ If a gravsearch query times out, a `504 Gateway Timeout` will be returned.
8080
A Gravsearch query can be written in either of the two
8181
[DSP-API v2 schemas](introduction.md#api-schema). The simple schema
8282
is easier to work with, and is sufficient if you don't need to query
83-
anything below the level of a Knora value. If your query needs to refer to
83+
anything below the level of a DSP-API value. If your query needs to refer to
8484
standoff markup, you must use the complex schema. Each query must use a single
8585
schema, with one exception (see [Date Comparisons](#date-comparisons)).
8686

8787
Gravsearch query results can be requested in the simple or complex schema;
8888
see [API Schema](introduction.md#api-schema).
8989

90-
All examples hereafter run with Knora started locally as documented in the section [Getting Started with DSP-API](../../04-publishing-deployment/getting-started.md). If you access another Knora-Stack, you can check the IRI of the ontology you are targeting by requesting the [ontologies metadata](ontology-information.md#querying-ontology-metadata).
90+
All examples hereafter run with the DSP stack started locally as documented in the section [Getting Started with DSP-API](../../04-publishing-deployment/getting-started.md). If you access another stack, you can check the IRI of the ontology you are targeting by requesting the [ontologies metadata](ontology-information.md#querying-ontology-metadata).
9191

9292
### Using the Simple Schema
9393

@@ -100,8 +100,7 @@ PREFIX knora-api: <http://api.knora.org/ontology/knora-api/simple/v2#>
100100
PREFIX incunabula: <http://0.0.0.0:3333/ontology/0803/incunabula/simple/v2#>
101101
```
102102

103-
In the simple schema, Knora values are represented as literals, which
104-
can be used `FILTER` expressions
103+
In the simple schema, DSP-API values are represented as literals, which can be used `FILTER` expressions
105104
(see [Filtering on Values in the Simple Schema](#filtering-on-values-in-the-simple-schema)).
106105

107106
### Using the Complex Schema
@@ -115,7 +114,7 @@ PREFIX knora-api: <http://api.knora.org/ontology/knora-api/v2#>
115114
PREFIX incunabula: <http://0.0.0.0:3333/ontology/0803/incunabula/v2#>
116115
```
117116

118-
In the complex schema, Knora values are represented as objects belonging
117+
In the complex schema, DSP-API values are represented as objects belonging
119118
to subclasses of `knora-api:Value`, e.g. `knora-api:TextValue`, and have
120119
predicates of their own, which can be used in `FILTER` expressions
121120
(see [Filtering on Values in the Complex Schema](#filtering-on-values-in-the-complex-schema)).
@@ -182,7 +181,7 @@ permission to see a matching dependent resource, the link value is hidden.
182181
## Paging
183182

184183
Gravsearch results are returned in pages. The maximum number of main
185-
resources per page is determined by Knora (and can be configured
184+
resources per page is determined by the API (and can be configured
186185
in `application.conf` via the setting `app/v2/resources-sequence/results-per-page`).
187186
If some resources have been filtered out because the user does not have
188187
permission to see them, a page could contain fewer results, or no results.
@@ -195,25 +194,20 @@ one at a time, until the response does not contain `knora-api:mayHaveMoreResults
195194
## Inference
196195

197196
Gravsearch queries are understood to imply a subset of
198-
[RDFS reasoning](https://www.w3.org/TR/rdf11-mt/). Depending on the
199-
triplestore being used, this may be implemented using the triplestore's
200-
own reasoner or by query expansion in Knora.
197+
[RDFS reasoning](https://www.w3.org/TR/rdf11-mt/). This is done by the API by expanding the incoming query.
201198

202199
Specifically, if a statement pattern specifies a property, the pattern will
203200
also match subproperties of that property, and if a statement specifies that
204201
a subject has a particular `rdf:type`, the statement will also match subjects
205202
belonging to subclasses of that type.
206203

207204
If you know that reasoning will not return any additional results for
208-
your query, you can disable it by adding this line to the `WHERE` clause:
205+
your query, you can disable it by adding this line to the `WHERE` clause, which may improve query performance:
209206

210207
```sparql
211208
knora-api:GravsearchOptions knora-api:useInference false .
212209
```
213210

214-
If Knora is implementing reasoning by query expansion, disabling it can
215-
improve the performance of some queries.
216-
217211
## Gravsearch Syntax
218212

219213
Every Gravsearch query is a valid SPARQL 1.1
@@ -244,8 +238,8 @@ clauses use the following patterns, with the specified restrictions:
244238
unordered set of triples. However, a Gravsearch query returns an
245239
ordered list of resources, which can be ordered by the values of
246240
specified properties. If the query is written in the complex schema,
247-
items below the level of Knora values may not be used in `ORDER BY`.
248-
- `BIND`: The value assigned must be a Knora resource IRI.
241+
items below the level of DSP-API values may not be used in `ORDER BY`.
242+
- `BIND`: The value assigned must be a DSP resource IRI.
249243

250244
### Resources, Properties, and Values
251245

@@ -269,7 +263,7 @@ must be represented as a query variable.
269263

270264
#### Filtering on Values in the Simple Schema
271265

272-
In the simple schema, a variable representing a Knora value can be used
266+
In the simple schema, a variable representing a DSP-API value can be used
273267
directly in a `FILTER` expression. For example:
274268

275269
```
@@ -279,7 +273,7 @@ FILTER(?title = "Zeitglöcklein des Lebens und Leidens Christi")
279273

280274
Here the type of `?title` is `xsd:string`.
281275

282-
The following Knora value types can be compared with literals in `FILTER`
276+
The following value types can be compared with literals in `FILTER`
283277
expressions in the simple schema:
284278

285279
- Text values (`xsd:string`)
@@ -295,7 +289,7 @@ performing an exact match on a list node's label. Labels can be given in differe
295289
If one of the given list node labels matches, it is considered a match.
296290
Note that in the simple schema, uniqueness is not guaranteed (as opposed to the complex schema).
297291

298-
A Knora value may not be represented as the literal object of a predicate;
292+
A DSP-API value may not be represented as the literal object of a predicate;
299293
for example, this is not allowed:
300294

301295
```
@@ -304,9 +298,9 @@ for example, this is not allowed:
304298

305299
#### Filtering on Values in the Complex Schema
306300

307-
In the complex schema, variables representing Knora values are not literals.
301+
In the complex schema, variables representing DSP-API values are not literals.
308302
You must add something to the query (generally a statement) to get a literal
309-
from a Knora value. For example:
303+
from a DSP-API value. For example:
310304

311305
```
312306
?book incunabula:title ?title .
@@ -479,7 +473,7 @@ within a single paragraph.
479473
If you are only interested in specifying that a resource has some text
480474
value containing a standoff link to another resource, the most efficient
481475
way is to use the property `knora-api:hasStandoffLinkTo`, whose subjects and objects
482-
are resources. This property is automatically maintained by Knora. For example:
476+
are resources. This property is automatically maintained by the API. For example:
483477

484478
```
485479
PREFIX knora-api: <http://api.knora.org/ontology/knora-api/v2#>
@@ -623,7 +617,7 @@ CONSTRUCT {
623617

624618
### Filtering on `rdfs:label`
625619

626-
The `rdfs:label` of a resource is not a Knora value, but you can still search for it.
620+
The `rdfs:label` of a resource is not a DSP-API value, but you can still search for it.
627621
This can be done in the same ways in the simple or complex schema:
628622

629623
Using a string literal object:
@@ -708,8 +702,8 @@ clause but not in the `CONSTRUCT` clause, the matching resources or values
708702
will not be included in the results.
709703

710704
If the query is written in the complex schema, all variables in the `CONSTRUCT`
711-
clause must refer to Knora resources, Knora values, or properties. Data below
712-
the level of Knora values may not be mentioned in the `CONSTRUCT` clause.
705+
clause must refer to DSP-API resources, DSP-API values, or properties. Data below
706+
the level of values may not be mentioned in the `CONSTRUCT` clause.
713707

714708
Predicates from the `rdf`, `rdfs`, and `owl` ontologies may not be used
715709
in the `CONSTRUCT` clause. The `rdfs:label` of each matching resource is always
@@ -921,7 +915,7 @@ adding statements with the predicate `rdf:type`. The subject must be a resource
921915
and the object must either be `knora-api:Resource` (if the subject is a resource)
922916
or the subject's specific type (if it is a value).
923917

924-
For example, consider this query that uses a non-Knora property:
918+
For example, consider this query that uses a non-DSP property:
925919

926920
```
927921
PREFIX incunabula: <http://0.0.0.0:3333/ontology/0803/incunabula/simple/v2#>
@@ -992,7 +986,7 @@ CONSTRUCT {
992986
Note that it only makes sense to use `dcterms:title` in the simple schema, because
993987
its object is supposed to be a literal.
994988

995-
Here is another example, using a non-Knora class:
989+
Here is another example, using a non-DSP class:
996990

997991
```
998992
PREFIX knora-api: <http://api.knora.org/ontology/knora-api/simple/v2#>

‎docs/05-internals/design/api-v2/gravsearch.md‎

Lines changed: 13 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ pattern orders must be optimised by moving `LuceneQueryPatterns` to the beginnin
128128
- `ConstructToConstructTransformer` (extends `WhereTransformer`): instructions how to turn a triplestore independent Construct query into a triplestore dependent Construct query (implementation of inference).
129129

130130
The traits listed above define methods that are implemented in the transformer classes and called by `QueryTraverser` to perform SPARQL to SPARQL conversions.
131-
When iterating over the statements of the input query, the transformer class's transformation methods are called to perform the conversion.
131+
When iterating over the statements of the input query, the transformer class' transformation methods are called to perform the conversion.
132132

133133
### Prequery
134134

@@ -152,7 +152,7 @@ Next, the Gravsearch query's WHERE clause is transformed and the prequery (SELEC
152152
The transformation of the Gravsearch query's WHERE clause relies on the implementation of the abstract class `AbstractPrequeryGenerator`.
153153

154154
`AbstractPrequeryGenerator` contains members whose state is changed during the iteration over the statements of the input query.
155-
They can then by used to create the converted query.
155+
They can then be used to create the converted query.
156156

157157
- `mainResourceVariable: Option[QueryVariable]`: SPARQL variable representing the main resource of the input query. Present in the prequery's SELECT clause.
158158
- `dependentResourceVariables: mutable.Set[QueryVariable]`: a set of SPARQL variables representing dependent resources in the input query. Used in an aggregation function in the prequery's SELECT clause (see below).
@@ -288,29 +288,12 @@ to the maximum allowed page size, the predicate
288288

289289
## Inference
290290

291-
Gravsearch queries support a subset of RDFS reasoning
292-
(see [Inference](../../../03-apis/api-v2/query-language.md#inference) in the API documentation
291+
Gravsearch queries support a subset of RDFS reasoning (see [Inference](../../../03-apis/api-v2/query-language.md#inference) in the API documentation
293292
on Gravsearch). This is implemented as follows:
294293

295-
When the non-triplestore-specific version of a SPARQL query is generated, statements that do not need
296-
inference are marked with the virtual named graph `<http://www.knora.org/explicit>`.
294+
To simulate RDF inference, the API expands the prequery on basis of the available ontologies. For that reason, `SparqlTransformer.transformStatementInWhereForNoInference` expands all `rdfs:subClassOf` and `rdfs:subPropertyOf` statements using `UNION` statements for all subclasses and subproperties from the ontologies (equivalent to `rdfs:subClassOf*` and `rdfs:subPropertyOf*`).
295+
Similarly, `SparqlTransformer.transformStatementInWhereForNoInference` replaces `knora-api:standoffTagHasStartAncestor` with `knora-base:standoffTagHasStartParent*`.
297296

298-
When the triplestore-specific version of the query is generated:
299-
300-
- If the triplestore is GraphDB, `SparqlTransformer.transformKnoraExplicitToGraphDBExplicit` changes statements
301-
with the virtual graph `<http://www.knora.org/explicit>` so that they are marked with the GraphDB-specific graph
302-
`<http://www.ontotext.com/explicit>`, and leaves other statements unchanged.
303-
`SparqlTransformer.transformKnoraExplicitToGraphDBExplicit` also adds the `valueHasString` statements which GraphDB needs
304-
for text searches.
305-
306-
- If Knora is not using the triplestore's inference (e.g. with Fuseki),
307-
`SparqlTransformer.transformStatementInWhereForNoInference` removes `<http://www.knora.org/explicit>`, and expands unmarked
308-
statements using `rdfs:subClassOf*` and `rdfs:subPropertyOf*`.
309-
310-
Gravsearch also provides some virtual properties, which take advantage of forward-chaining inference
311-
as an optimisation if the triplestore provides it. For example, the virtual property
312-
`knora-api:standoffTagHasStartAncestor` is equivalent to `knora-base:standoffTagHasStartParent*`. If Knora is not using the triplestore's inference, `SparqlTransformer.transformStatementInWhereForNoInference`
313-
replaces `knora-api:standoffTagHasStartAncestor` with `knora-base:standoffTagHasStartParent*`.
314297

315298
# Optimisation of generated SPARQL
316299

@@ -320,8 +303,7 @@ Lucene queries to the beginning of the block in which they occur.
320303

321304
## Query Optimization by Topological Sorting of Statements
322305

323-
GraphDB seems to have inherent algorithms to optimize the query time, however query performance of Fuseki highly depends
324-
on the order of the query statements. For example, a query such as the one below:
306+
In Jena Fuseki, the performance of a query highly depends on the order of the query statements. For example, a query such as the one below:
325307

326308
```sparql
327309
PREFIX beol: <http://0.0.0.0:3333/ontology/0801/beol/v2#>
@@ -370,8 +352,7 @@ The rest of the query then reads:
370352
?letter beol:creationDate ?date .
371353
```
372354

373-
Since we cannot expect clients to know about performance of triplestores in order to write efficient queries, we have
374-
implemented an optimization method to automatically rearrange the statements of the given queries.
355+
Since users cannot be expected to know about performance of triplestores in order to write efficient queries, an optimization method to automatically rearrange the statements of the given queries has been implemented.
375356
Upon receiving the Gravsearch query, the algorithm converts the query to a graph. For each statement pattern,
376357
the subject of the statement is the origin node, the predicate is a directed edge, and the object
377358
is the target node. For the query above, this conversion would result in the following graph:
@@ -384,17 +365,16 @@ topological sorting algorithm](https://en.wikipedia.org/wiki/Topological_sorting
384365
The algorithm returns the nodes of the graph ordered in several layers, where the
385366
root element `?letter` is in layer 0, `[?date, ?person1, ?person2]` are in layer 1, `[?gnd1, ?gnd2]` in layer 2, and the
386367
leaf nodes `[(DE-588)118531379, (DE-588)118696149]` are given in the last layer (i.e. layer 3).
387-
According to Kahn's algorithm, there are multiple valid permutations of the topological order. The graph in the example
388-
above has 24 valid permutations of topological order. Here are two of them (nodes are ordered from left to right with the highest
389-
order to the lowest):
368+
According to Kahn's algorithm, there are multiple valid permutations of the topological order. The graph in the example
369+
above has 24 valid permutations of topological order. Here are two of them (nodes are ordered from left to right with the
370+
highest order to the lowest):
390371

391372
- `(?letter, ?date, ?person2, ?person1, ?gnd2, ?gnd1, (DE-588)118696149, (DE-588)118531379)`
392373
- `(?letter, ?date, ?person1, ?person2, ?gnd1, ?gnd2, (DE-588)118531379, (DE-588)118696149)`.
393374

394-
From all valid topological orders, one is chosen based on certain criteria; for example, the leaf should node should not
375+
From all valid topological orders, one is chosen based on certain criteria; for example, the leaf node should not
395376
belong to a statement that has predicate `rdf:type`, since that could match all resources of the specified type.
396-
Once the best order is chosen, it is used to re-arrange the query
397-
statements. Starting from the last leaf node, i.e.
377+
Once the best order is chosen, it is used to re-arrange the query statements. Starting from the last leaf node, i.e.
398378
`(DE-588)118696149`, the method finds the statement pattern which has this node as its object, and brings this statement
399379
to the top of the query. This rearrangement continues so that the statements with the fewest dependencies on other
400380
statements are all brought to the top of the query. The resulting query is as follows:
@@ -423,8 +403,7 @@ CONSTRUCT {
423403

424404
Note that position of the FILTER statements does not play a significant role in the optimization.
425405

426-
If a Gravsearch query contains statements in `UNION`, `OPTIONAL`, `MINUS`, or
427-
`FILTER NOT EXISTS`, they are reordered
406+
If a Gravsearch query contains statements in `UNION`, `OPTIONAL`, `MINUS`, or `FILTER NOT EXISTS`, they are reordered
428407
by defining a graph per block. For example, consider the following query with `UNION`:
429408

430409
```sparql

0 commit comments

Comments
 (0)