@lde/pipeline-void 0.37.0 → 0.37.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/stage.js CHANGED
@@ -119,7 +119,7 @@ export function subjectUriSpaces(options) {
119
119
  export function classPartitions(options) {
120
120
  return createVoidStage(VOID_STAGE_NAMES.classPartitions, options);
121
121
  }
122
- // Scalar-aggregate counts: each query is a single COUNT with no GROUP BY/HAVING,
122
+ // Scalar-aggregate counts: each query ends in one COUNT without GROUP BY/HAVING,
123
123
  // so it always returns exactly one row. Zero output therefore means the endpoint
124
124
  // truncated the response, so they set `expectsOutput` to fail (and trigger the
125
125
  // reactive dump fallback) rather than store a missing count.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lde/pipeline-void",
3
- "version": "0.37.0",
3
+ "version": "0.37.1",
4
4
  "description": "VOiD (Vocabulary of Interlinked Datasets) statistical analysis for RDF datasets",
5
5
  "repository": {
6
6
  "url": "git+https://github.com/ldelements/lde.git",
@@ -29,11 +29,11 @@
29
29
  "@rdfjs/types": "^2.0.1",
30
30
  "@tpluscode/rdf-ns-builders": "^5.0.0",
31
31
  "@zazuko/prefixes": "^2.6.1",
32
- "n3": "^2.7.12",
32
+ "n3": "^2.13.12",
33
33
  "tslib": "^2.3.0"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "@lde/dataset": "^0.8.0",
37
- "@lde/pipeline": "^0.38.0"
37
+ "@lde/pipeline": "^0.38.1"
38
38
  }
39
39
  }
@@ -6,9 +6,14 @@ CONSTRUCT {
6
6
  void-ext:distinctLiterals ?distinctLiterals .
7
7
  }
8
8
  WHERE {
9
- SELECT (COUNT(DISTINCT ?o) AS ?distinctLiterals) {
10
- #subjectFilter#
11
- ?s ?p ?o .
12
- FILTER(ISLITERAL(?o))
9
+ # Count groups instead of COUNT(DISTINCT): QLever then needs far less memory,
10
+ # and Fuseki and Virtuoso are as fast. A DISTINCT subquery ran Fuseki out of memory.
11
+ SELECT (COUNT(*) AS ?distinctLiterals) {
12
+ SELECT ?o {
13
+ #subjectFilter#
14
+ ?s ?p ?o .
15
+ FILTER(ISLITERAL(?o))
16
+ }
17
+ GROUP BY ?o
13
18
  }
14
19
  }
@@ -6,9 +6,14 @@ CONSTRUCT {
6
6
  void-ext:distinctIRIReferenceObjects ?distinctIRIReferenceObjects .
7
7
  }
8
8
  WHERE {
9
- SELECT (COUNT(DISTINCT ?o) AS ?distinctIRIReferenceObjects) {
10
- #subjectFilter#
11
- ?s ?p ?o .
12
- FILTER(ISIRI(?o))
9
+ # Count groups instead of COUNT(DISTINCT): QLever then needs far less memory,
10
+ # and Fuseki and Virtuoso are as fast.
11
+ SELECT (COUNT(*) AS ?distinctIRIReferenceObjects) {
12
+ SELECT ?o {
13
+ #subjectFilter#
14
+ ?s ?p ?o .
15
+ FILTER(ISIRI(?o))
16
+ }
17
+ GROUP BY ?o
13
18
  }
14
19
  }
@@ -10,31 +10,41 @@ WHERE {
10
10
  {
11
11
  SELECT ?uriSpace (SUM(?groupCount) AS ?count) {
12
12
  {
13
- # Group subjects by the directory of their document (the IRI without fragment),
14
- # keeping hash IRIs apart. MIN = MAX tells whether a hash group holds a single
15
- # document, without the cost of COUNT(DISTINCT ?document).
16
- SELECT ?directory ?isHash (COUNT(*) AS ?groupCount)
17
- (MIN(?document) AS ?firstDocument) (MAX(?document) AS ?lastDocument) {
13
+ # Group the keys by directory, keeping hash IRIs apart. The number of keys in a
14
+ # hash group is the number of documents it holds.
15
+ SELECT ?directory ?isHash (SUM(?keyCount) AS ?groupCount)
16
+ (COUNT(*) AS ?documents) (MIN(?key) AS ?firstKey) {
18
17
  {
19
- # Pre-group distinct subjects before computing namespace to avoid regex on every triple.
20
- SELECT DISTINCT ?s {
21
- #subjectFilter#
22
- ?s ?p ?o .
23
- FILTER(ISIRI(?s))
18
+ # Reduce each distinct subject to one key with a single REPLACE: its
19
+ # document followed by "#" for a hash IRI, its directory otherwise.
20
+ # Grouping on that key first keeps the per-subject strings to one. The
21
+ # pattern uses + rather than *, because Virtuoso rejects a pattern that
22
+ # matches the empty string; an IRI ending in / is its own key either way.
23
+ SELECT ?key (COUNT(*) AS ?keyCount) {
24
+ {
25
+ SELECT DISTINCT ?s {
26
+ #subjectFilter#
27
+ ?s ?p ?o .
28
+ FILTER(ISIRI(?s))
29
+ }
30
+ }
31
+ BIND(REPLACE(STR(?s), "(#).*$|[^/#]+$", "$1") AS ?key)
24
32
  }
33
+ GROUP BY ?key
25
34
  }
26
- BIND(STR(?s) AS ?iri)
27
- BIND(CONTAINS(?iri, "#") AS ?isHash)
28
- BIND(IF(?isHash, STRBEFORE(?iri, "#"), ?iri) AS ?document)
29
- BIND(REPLACE(?document, "[^/]*$", "") AS ?prefix)
30
- # A document without path (http://host, urn:…) is its own directory.
31
- BIND(IF(?prefix = "" || STRENDS(?prefix, "//"), ?document, ?prefix) AS ?directory)
35
+ BIND(STRENDS(?key, "#") AS ?isHash)
36
+ BIND(IF(?isHash, REPLACE(?key, "[^/]*#$", ""), ?key) AS ?prefix)
37
+ # A subject without hash and without path (http://host, urn:…) is a URI
38
+ # space of its own with one entity, which the count filter below drops.
39
+ FILTER(?isHash || (?prefix != "" && !STRENDS(?prefix, "//")))
40
+ # A hash document without path is its own directory.
41
+ BIND(IF(?prefix = "" || STRENDS(?prefix, "//"), STRBEFORE(?key, "#"), ?prefix) AS ?directory)
32
42
  }
33
43
  GROUP BY ?directory ?isHash
34
44
  }
35
45
  # A hash vocabulary (…/skos/core#) keeps its document as namespace; hash IRIs spread
36
46
  # over several documents (…/record1#media, …/record2#media) share their directory.
37
- BIND(IF(?isHash && ?firstDocument = ?lastDocument, CONCAT(?firstDocument, "#"), ?directory) AS ?uriSpace)
47
+ BIND(IF(?isHash && ?documents = 1, ?firstKey, ?directory) AS ?uriSpace)
38
48
  }
39
49
  GROUP BY ?uriSpace
40
50
  ORDER BY DESC(?count)
@@ -43,4 +53,3 @@ WHERE {
43
53
  FILTER(?count > 1) # Only include namespaces with more than 1 entity
44
54
  BIND(URI(CONCAT(STR(?dataset), "/.well-known/void#subject-uri-space-", MD5(?uriSpace))) AS ?subset)
45
55
  }
46
-
@@ -5,9 +5,14 @@ CONSTRUCT {
5
5
  void:distinctSubjects ?count .
6
6
  }
7
7
  WHERE {
8
- SELECT (COUNT(DISTINCT ?s) as ?count) {
9
- #subjectFilter#
10
- ?s ?p ?o .
11
- FILTER(!ISBLANK(?s))
8
+ # Count groups instead of COUNT(DISTINCT): QLever then needs far less memory,
9
+ # Virtuoso half the time, and Fuseki is as fast.
10
+ SELECT (COUNT(*) AS ?count) {
11
+ SELECT ?s {
12
+ #subjectFilter#
13
+ ?s ?p ?o .
14
+ FILTER(!ISBLANK(?s))
15
+ }
16
+ GROUP BY ?s
12
17
  }
13
18
  }