@lde/pipeline 0.39.0 → 0.39.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.
@@ -10,7 +10,10 @@ export declare function findSubSelect(patterns: Pattern[]): QuerySelect | undefi
10
10
  *
11
11
  * Each row's keys become SPARQL variables; NamedNode values become IRIs in the
12
12
  * VALUES block. The VALUES clause is injected into the innermost subquery so
13
- * that SPARQL engines can constrain scans early.
13
+ * that SPARQL engines can constrain scans early. When that WHERE clause is a
14
+ * single UNION whose branches all bind the VALUES variables in a required
15
+ * triple pattern, the VALUES goes into each branch instead: engines such as
16
+ * QLever plan and evaluate each branch without the outer VALUES otherwise.
14
17
  *
15
18
  * The caller owns parsing and stringifying; this function operates on the AST.
16
19
  */
@@ -1 +1 @@
1
- {"version":3,"file":"values.d.ts","sourceRoot":"","sources":["../../src/sparql/values.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,OAAO,EAGZ,KAAK,cAAc,EACnB,KAAK,WAAW,EAEjB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAIpD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,WAAW,GAAG,SAAS,CAW1E;AA0DD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,cAAc,EACrB,QAAQ,EAAE,gBAAgB,EAAE,GAC3B,cAAc,CAoBhB"}
1
+ {"version":3,"file":"values.d.ts","sourceRoot":"","sources":["../../src/sparql/values.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,OAAO,EAIZ,KAAK,cAAc,EACnB,KAAK,WAAW,EAEjB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAIpD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,WAAW,GAAG,SAAS,CAW1E;AA0GD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,cAAc,EACrB,QAAQ,EAAE,gBAAgB,EAAE,GAC3B,cAAc,CAoBhB"}
@@ -47,8 +47,9 @@ function mapSubSelect(patterns, replacer) {
47
47
  * clause into the innermost WHERE clause. This ensures that SPARQL engines
48
48
  * constrain scans at the deepest level rather than only at the outer scope.
49
49
  *
50
- * For flat queries (no SubSelect), the base case injects directly — identical
51
- * to the previous behavior.
50
+ * For flat queries (no SubSelect), the base case injects directly: in front of
51
+ * the WHERE clause, or into each branch when it is a single UNION whose branches
52
+ * all qualify (see {@link bindsAllVariables}).
52
53
  */
53
54
  function injectIntoInnermost(where, valuesPattern) {
54
55
  const mapped = mapSubSelect(where.patterns, (subSelect) => ({
@@ -56,17 +57,54 @@ function injectIntoInnermost(where, valuesPattern) {
56
57
  where: injectIntoInnermost(subSelect.where, valuesPattern),
57
58
  }));
58
59
  if (!mapped) {
59
- // Base case: no SubSelect — inject here.
60
+ // Base case: no SubSelect – inject here, into each UNION branch if safe.
61
+ const branches = where.patterns.length === 1 && F.isPatternUnion(where.patterns[0])
62
+ ? where.patterns[0].patterns
63
+ : [];
64
+ if (branches.length > 0 &&
65
+ branches.every((branch) => bindsAllVariables(branch, valuesPattern))) {
66
+ return F.patternGroup([
67
+ F.patternUnion(branches.map((branch) => F.patternGroup([valuesPattern, ...branch.patterns], F.gen())), F.gen()),
68
+ ], F.gen());
69
+ }
60
70
  return F.patternGroup([valuesPattern, ...where.patterns], F.gen());
61
71
  }
62
72
  return F.patternGroup(mapped, F.gen());
63
73
  }
74
+ /**
75
+ * Whether a UNION branch binds every VALUES variable in a triple pattern of a
76
+ * top-level BGP that precedes the branch’s first OPTIONAL, MINUS, BIND or other
77
+ * non-FILTER pattern. Those apply to the solutions before them, so only then
78
+ * does joining the VALUES first in the branch return the same solutions as
79
+ * joining it outside the UNION. FILTERs apply to the whole group, wherever they are.
80
+ */
81
+ function bindsAllVariables(branch, valuesPattern) {
82
+ const bound = new Set();
83
+ for (const pattern of branch.patterns) {
84
+ if (pattern.subType === 'filter')
85
+ continue;
86
+ if (!F.isPatternBgp(pattern))
87
+ break;
88
+ for (const triple of pattern.triples) {
89
+ if (triple.type !== 'triple')
90
+ continue;
91
+ for (const term of [triple.subject, triple.predicate, triple.object]) {
92
+ if (F.isTermVariable(term))
93
+ bound.add(term.value);
94
+ }
95
+ }
96
+ }
97
+ return valuesPattern.variables.every((variable) => bound.has(variable.value));
98
+ }
64
99
  /**
65
100
  * Inject a VALUES clause into a parsed CONSTRUCT query for the given binding rows.
66
101
  *
67
102
  * Each row's keys become SPARQL variables; NamedNode values become IRIs in the
68
103
  * VALUES block. The VALUES clause is injected into the innermost subquery so
69
- * that SPARQL engines can constrain scans early.
104
+ * that SPARQL engines can constrain scans early. When that WHERE clause is a
105
+ * single UNION whose branches all bind the VALUES variables in a required
106
+ * triple pattern, the VALUES goes into each branch instead: engines such as
107
+ * QLever plan and evaluate each branch without the outer VALUES otherwise.
70
108
  *
71
109
  * The caller owns parsing and stringifying; this function operates on the AST.
72
110
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lde/pipeline",
3
- "version": "0.39.0",
3
+ "version": "0.39.1",
4
4
  "repository": {
5
5
  "url": "git+https://github.com/ldelements/lde.git",
6
6
  "directory": "packages/pipeline"