solis 0.124.0 → 0.126.0
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.
- checksums.yaml +4 -4
- data/lib/solis/query.rb +65 -8
- data/lib/solis/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ce5b23eb71b6b7189377d4981fa8e6dc076128d60aec207e58310fbd162b8a16
|
|
4
|
+
data.tar.gz: 57c514df94596d89d76ea012cc946b2b521869f585f1d495609f8e0ec5cbdf71
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 912773a18a5b44da59e1bbb6b7e3472c5d8be9a8044df0b64acbb7da501a3156ead5615363bd23fbd595d42db1b862f3d776235758d92acf8d0942f42f2fde00
|
|
7
|
+
data.tar.gz: 90c434c26f367159078555cf9bfbc715d4179ad8f3ebffba12ba379000e7696bf25020cd9a033d90b1b1e95375574bd4600eb3492d67e703a07aaff901d6b251
|
data/lib/solis/query.rb
CHANGED
|
@@ -11,6 +11,15 @@ module Solis
|
|
|
11
11
|
include Enumerable
|
|
12
12
|
include Solis::QueryFilter
|
|
13
13
|
|
|
14
|
+
# XSD numeric datatypes must be ordered by value; wrapping them in STR() would sort
|
|
15
|
+
# them lexically ("100" < "9"). Every other datatype is STR()-wrapped in the outer
|
|
16
|
+
# ORDER BY (see #sort_key_expression) to dodge a Virtuoso collation bug.
|
|
17
|
+
XSD_NUMERIC_DATATYPES = %w[
|
|
18
|
+
integer decimal float double long int short byte
|
|
19
|
+
nonNegativeInteger nonPositiveInteger negativeInteger positiveInteger
|
|
20
|
+
unsignedLong unsignedInt unsignedShort unsignedByte
|
|
21
|
+
].map { |t| "http://www.w3.org/2001/XMLSchema##{t}" }.freeze
|
|
22
|
+
|
|
14
23
|
def self.run(entity, query, options = {})
|
|
15
24
|
Solis::Query::Runner.run(entity, query, options)
|
|
16
25
|
end
|
|
@@ -96,6 +105,13 @@ module Solis
|
|
|
96
105
|
@filter = {values: ["VALUES ?type {#{target_class}}"], concepts: ['?concept a ?type .'] }
|
|
97
106
|
@sort = 'ORDER BY ?concept'
|
|
98
107
|
@sort_select = ''
|
|
108
|
+
# @sort_project carries the sort key(s) out of the inner (paginated) subquery so the
|
|
109
|
+
# outer query can re-sort on them; @sort_outer is that outer ORDER BY. A subquery's
|
|
110
|
+
# ORDER BY only decides which rows survive LIMIT/OFFSET — it does NOT propagate
|
|
111
|
+
# through the enclosing join — so the outer query must sort too. See #sort for the
|
|
112
|
+
# datatype-aware STR() handling that the outer ORDER BY needs.
|
|
113
|
+
@sort_project = ''
|
|
114
|
+
@sort_outer = ''
|
|
99
115
|
@language = Graphiti.context[:object]&.language || Solis::Options.instance.get[:language] || 'en'
|
|
100
116
|
@query_cache = self.class.shared_query_cache
|
|
101
117
|
end
|
|
@@ -113,19 +129,31 @@ module Solis
|
|
|
113
129
|
def sort(params)
|
|
114
130
|
@sort = ''
|
|
115
131
|
@sort_select = ''
|
|
132
|
+
@sort_project = ''
|
|
133
|
+
@sort_outer = ''
|
|
116
134
|
if params.key?(:sort)
|
|
117
135
|
i = 0
|
|
136
|
+
outer = ''
|
|
118
137
|
params[:sort].each do |attribute, direction|
|
|
119
|
-
|
|
120
|
-
|
|
138
|
+
meta = @model.class.metadata[:attributes][attribute.to_s]
|
|
139
|
+
path = meta[:path]
|
|
140
|
+
@sort_select += "optional {\n" if meta[:mincount] == 0
|
|
121
141
|
@sort_select += "?concept <#{path}> ?__#{attribute} . "
|
|
122
|
-
@sort_select += "}\n" if
|
|
142
|
+
@sort_select += "}\n" if meta[:mincount] == 0
|
|
123
143
|
@sort += ',' if i.positive?
|
|
124
144
|
@sort += "#{direction.to_s.upcase}(?__#{attribute})"
|
|
145
|
+
# Carry the sort key out of the subquery so the outer query can re-sort on it.
|
|
146
|
+
@sort_project += " ?__#{attribute}"
|
|
147
|
+
outer += ',' if i.positive?
|
|
148
|
+
outer += "#{direction.to_s.upcase}(#{sort_key_expression(attribute, meta)})"
|
|
125
149
|
i += 1
|
|
126
150
|
end
|
|
127
151
|
|
|
128
|
-
|
|
152
|
+
if i.positive?
|
|
153
|
+
@sort = "ORDER BY #{@sort}"
|
|
154
|
+
# ?s tiebreaker keeps each subject's triples contiguous and ties deterministic.
|
|
155
|
+
@sort_outer = "ORDER BY #{outer} ?s"
|
|
156
|
+
end
|
|
129
157
|
end
|
|
130
158
|
|
|
131
159
|
self
|
|
@@ -214,16 +242,27 @@ module Solis
|
|
|
214
242
|
core_query += " LIMIT #{limit} OFFSET #{offset}"
|
|
215
243
|
end
|
|
216
244
|
|
|
245
|
+
# ?o is bound via BIND(?o_raw AS ?o) instead of being projected directly from the
|
|
246
|
+
# triple pattern. Some Virtuoso builds (e.g. 08.03.3335) mis-bind ?o to the wrong
|
|
247
|
+
# object when the open `?s ?p ?o` pattern is combined with the disjunctive
|
|
248
|
+
# language_filter, returning the rdf:type object for every row. Aliasing through
|
|
249
|
+
# BIND blocks that optimizer rewrite and is a no-op on engines without the bug.
|
|
250
|
+
# Outer ordering: the inner subquery's ORDER BY only selects which rows survive
|
|
251
|
+
# LIMIT/OFFSET; it does NOT propagate through the enclosing join. The outer query
|
|
252
|
+
# therefore re-sorts on the same key(s) (@sort_outer, datatype-aware STR()). When
|
|
253
|
+
# no sort is requested, `order by ?s` gives a cheap deterministic default order.
|
|
254
|
+
outer_order = @sort_outer.empty? ? 'order by ?s' : @sort_outer
|
|
217
255
|
query = %(
|
|
218
256
|
#{prefixes}
|
|
219
257
|
SELECT ?s ?p ?o WHERE {
|
|
220
|
-
?s ?p ?
|
|
258
|
+
?s ?p ?o_raw .
|
|
259
|
+
BIND(?o_raw AS ?o)
|
|
221
260
|
{
|
|
222
261
|
#{core_query}
|
|
223
262
|
}
|
|
224
263
|
#{language_filter}
|
|
225
264
|
}
|
|
226
|
-
|
|
265
|
+
#{outer_order}
|
|
227
266
|
)
|
|
228
267
|
|
|
229
268
|
Solis::LOGGER.info(query) if ConfigFile[:debug]
|
|
@@ -268,10 +307,13 @@ order by ?s
|
|
|
268
307
|
end
|
|
269
308
|
|
|
270
309
|
def core_query(relationship)
|
|
310
|
+
# @sort_project re-exposes the sort key(s) bound in @sort_select so the OUTER query
|
|
311
|
+
# can ORDER BY them. NB: for a multi-valued sort attribute this turns DISTINCT into
|
|
312
|
+
# one row per (concept, value) pair, which would skew LIMIT/OFFSET — sort attributes
|
|
313
|
+
# are expected to be single-valued.
|
|
271
314
|
core_query = %(
|
|
272
|
-
SELECT distinct (?concept AS ?s) WHERE {
|
|
315
|
+
SELECT distinct (?concept AS ?s)#{@sort_project} WHERE {
|
|
273
316
|
#{@filter[:values].join("\n")}
|
|
274
|
-
?concept ?role ?objects.
|
|
275
317
|
#{relationship}
|
|
276
318
|
#{@filter[:concepts].join("\n")}
|
|
277
319
|
|
|
@@ -281,6 +323,21 @@ order by ?s
|
|
|
281
323
|
)
|
|
282
324
|
end
|
|
283
325
|
|
|
326
|
+
# SPARQL expression to sort an attribute by in the OUTER query.
|
|
327
|
+
#
|
|
328
|
+
# The outer (post-join) ORDER BY on this Virtuoso build (08.03.3335) mis-collates
|
|
329
|
+
# language-tagged literals, so string-ish keys are wrapped in STR() — which fixes the
|
|
330
|
+
# ordering and is order-preserving for xsd:string, dates, gYear, booleans and EDTF.
|
|
331
|
+
# Numeric keys must NOT be wrapped: STR() would order them lexically ("100" < "9"),
|
|
332
|
+
# and numbers carry no language tag so they are unaffected by the collation bug.
|
|
333
|
+
def sort_key_expression(attribute, meta = @model.class.metadata[:attributes][attribute.to_s])
|
|
334
|
+
if XSD_NUMERIC_DATATYPES.include?(meta[:datatype_rdf].to_s)
|
|
335
|
+
"?__#{attribute}"
|
|
336
|
+
else
|
|
337
|
+
"STR(?__#{attribute})"
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
284
341
|
def prefixes
|
|
285
342
|
"
|
|
286
343
|
PREFIX sh: <http://www.w3.org/ns/shacl#>
|
data/lib/solis/version.rb
CHANGED