Skip to content

Commit 9077e10

Browse files
namedgraphclaude
andcommitted
Link-predicate discovery groups each direction before the UNION
The pivot discovery query joined the view once with an outgoing/incoming UNION and deduplicated with a top-level DISTINCT, materialising every joined triple first. Each direction now joins the view in its own subquery and collapses to its predicates with GROUP BY: SELECT ?predicate ?inverse { { SELECT ?predicate (false AS ?inverse) { { <view> } <outgoing> } GROUP BY ?predicate } UNION { SELECT ?predicate (true AS ?inverse) { { <view> } <incoming> } GROUP BY ?predicate } } LIMIT 100 Same rows. On a view whose results have millions of incoming triples (linkeddata.lt persons/, ~4.9M roll-call vote quads) 27.7 s -> 8.5 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 845c68e commit 9077e10

1 file changed

Lines changed: 112 additions & 81 deletions

File tree

‎src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/client/query-transforms.xsl‎

Lines changed: 112 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -233,9 +233,20 @@ extension-element-prefixes="ixsl"
233233

234234
<!-- Turns the view's SELECT into a query for the predicates that link its results to the rest
235235
of the graph: ?predicate paired with ?inverse, which flags the incoming ones. The SELECT
236-
becomes a subquery (in its own group - a SubSelect has to be the sole content of its
237-
braces, and the WHERE has a second member), so discovery covers the whole result set
238-
rather than the loaded page; the caller strips LIMIT/OFFSET/ORDER off it first.
236+
becomes a subquery, so discovery covers the whole result set rather than the loaded page;
237+
the caller strips LIMIT/OFFSET/ORDER off it first.
238+
239+
SELECT ?predicate ?inverse {
240+
{ SELECT ?predicate (false AS ?inverse) { { <view> } <outgoing> } GROUP BY ?predicate }
241+
UNION
242+
{ SELECT ?predicate (true AS ?inverse) { { <view> } <incoming> } GROUP BY ?predicate }
243+
} LIMIT 100
244+
245+
Each direction joins the view on its own and collapses to its predicates with GROUP BY before
246+
the UNION. Joining the view once with an outgoing/incoming UNION and deduplicating with a
247+
top-level DISTINCT returns the same rows, but materialises every joined triple first: on a
248+
view whose results have millions of incoming triples (Seimas members and their ~4.9M roll-call
249+
votes) that took 27.7 s, this takes 8.5 s.
239250
240251
Outgoing objects that are literals, or that are never subjects themselves, are filtered
241252
out: a pivot onto them would render an empty view. Incoming subjects need no such check -
@@ -249,111 +260,131 @@ extension-element-prefixes="ixsl"
249260
<xsl:template match="/json:map" mode="ldh:link-predicates" priority="1">
250261
<xsl:param name="suffix" select="ldh:fresh-var-suffix(., ('predicate', 'inverse', 'object', 'subject'))" as="xs:string" tunnel="yes"/>
251262
<xsl:param name="limit" select="100" as="xs:integer" tunnel="yes"/>
252-
<xsl:variable name="focus-var" select="'?' || ldh:first-var-name(.)" as="xs:string"/>
253-
<xsl:variable name="predicate-var" select="'?predicate' || $suffix" as="xs:string"/>
254-
<xsl:variable name="inverse-var" select="'?inverse' || $suffix" as="xs:string"/>
255-
<xsl:variable name="object-var" select="'?object' || $suffix" as="xs:string"/>
256-
<xsl:variable name="subject-var" select="'?subject' || $suffix" as="xs:string"/>
263+
<xsl:variable name="select" select="." as="element(json:map)"/>
257264

258265
<json:map>
259266
<json:string key="queryType">SELECT</json:string>
260-
<json:boolean key="distinct">true</json:boolean>
261267
<json:array key="variables">
262-
<json:string><xsl:value-of select="$predicate-var"/></json:string>
263-
<json:string><xsl:value-of select="$inverse-var"/></json:string>
268+
<json:string><xsl:value-of select="'?predicate' || $suffix"/></json:string>
269+
<json:string><xsl:value-of select="'?inverse' || $suffix"/></json:string>
264270
</json:array>
265271
<!-- hoist the dataset clause, which is only legal on the outermost query (see ldh:wrap-describe) -->
266272
<xsl:copy-of select="json:map[@key = 'from']"/>
267273
<json:array key="where">
268274
<json:map>
269-
<json:string key="type">group</json:string>
275+
<json:string key="type">union</json:string>
270276
<json:array key="patterns">
271-
<xsl:apply-templates select="." mode="ldh:strip-from"/>
277+
<xsl:for-each select="(false(), true())">
278+
<json:map>
279+
<json:string key="type">group</json:string>
280+
<json:array key="patterns">
281+
<xsl:call-template name="ldh:LinkPredicatesSubquery">
282+
<xsl:with-param name="select" select="$select"/>
283+
<xsl:with-param name="suffix" select="$suffix"/>
284+
<xsl:with-param name="inverse" select="."/>
285+
</xsl:call-template>
286+
</json:array>
287+
</json:map>
288+
</xsl:for-each>
272289
</json:array>
273290
</json:map>
291+
</json:array>
292+
<json:number key="limit"><xsl:value-of select="$limit"/></json:number>
293+
</json:map>
294+
</xsl:template>
295+
296+
<!-- one direction of the link-predicate discovery: the view's results joined with their outgoing (or incoming) triples, grouped by predicate -->
297+
<xsl:template name="ldh:LinkPredicatesSubquery">
298+
<xsl:param name="select" as="element(json:map)"/>
299+
<xsl:param name="suffix" as="xs:string"/>
300+
<xsl:param name="inverse" as="xs:boolean"/>
301+
<xsl:variable name="focus-var" select="'?' || ldh:first-var-name($select)" as="xs:string"/>
302+
<xsl:variable name="predicate-var" select="'?predicate' || $suffix" as="xs:string"/>
303+
<xsl:variable name="object-var" select="'?object' || $suffix" as="xs:string"/>
274304

305+
<json:map>
306+
<json:string key="type">query</json:string>
307+
<json:string key="queryType">SELECT</json:string>
308+
<json:array key="variables">
309+
<json:string><xsl:value-of select="$predicate-var"/></json:string>
275310
<json:map>
276-
<json:string key="type">union</json:string>
311+
<json:string key="expression">"<xsl:value-of select="$inverse"/>"^^&xsd;boolean</json:string>
312+
<json:string key="variable"><xsl:value-of select="'?inverse' || $suffix"/></json:string>
313+
</json:map>
314+
</json:array>
315+
<json:array key="where">
316+
<!-- the view's SELECT in its own group: a SubSelect has to be the sole content of its braces -->
317+
<json:map>
318+
<json:string key="type">group</json:string>
277319
<json:array key="patterns">
278-
<!-- outgoing: the result set's own properties -->
279-
<json:map>
280-
<json:string key="type">group</json:string>
281-
<json:array key="patterns">
282-
<xsl:call-template name="ldh:GraphUnionPattern">
283-
<xsl:with-param name="subject" select="$focus-var"/>
284-
<xsl:with-param name="predicate" select="$predicate-var"/>
285-
<xsl:with-param name="object" select="$object-var"/>
286-
<xsl:with-param name="graph-var" select="'?graph' || $suffix"/>
287-
</xsl:call-template>
320+
<xsl:apply-templates select="$select" mode="ldh:strip-from"/>
321+
</json:array>
322+
</json:map>
288323

289-
<json:map>
290-
<json:string key="type">filter</json:string>
291-
<json:map key="expression">
292-
<json:string key="type">operation</json:string>
293-
<json:string key="operator">!</json:string>
294-
<json:array key="args">
295-
<json:map>
296-
<json:string key="type">operation</json:string>
297-
<json:string key="operator">isliteral</json:string>
298-
<json:array key="args">
299-
<json:string><xsl:value-of select="$object-var"/></json:string>
300-
</json:array>
301-
</json:map>
302-
</json:array>
303-
</json:map>
304-
</json:map>
324+
<xsl:choose>
325+
<!-- incoming: what points at the result set -->
326+
<xsl:when test="$inverse">
327+
<xsl:call-template name="ldh:GraphUnionPattern">
328+
<xsl:with-param name="subject" select="'?subject' || $suffix"/>
329+
<xsl:with-param name="predicate" select="$predicate-var"/>
330+
<xsl:with-param name="object" select="$focus-var"/>
331+
<xsl:with-param name="graph-var" select="'?subjectGraph' || $suffix"/>
332+
</xsl:call-template>
333+
</xsl:when>
334+
<!-- outgoing: the result set's own properties -->
335+
<xsl:otherwise>
336+
<xsl:call-template name="ldh:GraphUnionPattern">
337+
<xsl:with-param name="subject" select="$focus-var"/>
338+
<xsl:with-param name="predicate" select="$predicate-var"/>
339+
<xsl:with-param name="object" select="$object-var"/>
340+
<xsl:with-param name="graph-var" select="'?graph' || $suffix"/>
341+
</xsl:call-template>
305342

306-
<json:map>
307-
<json:string key="type">filter</json:string>
308-
<json:map key="expression">
343+
<json:map>
344+
<json:string key="type">filter</json:string>
345+
<json:map key="expression">
346+
<json:string key="type">operation</json:string>
347+
<json:string key="operator">!</json:string>
348+
<json:array key="args">
349+
<json:map>
309350
<json:string key="type">operation</json:string>
310-
<json:string key="operator">exists</json:string>
351+
<json:string key="operator">isliteral</json:string>
311352
<json:array key="args">
312-
<json:map>
313-
<json:string key="type">group</json:string>
314-
<json:array key="patterns">
315-
<xsl:call-template name="ldh:GraphUnionPattern">
316-
<xsl:with-param name="subject" select="$object-var"/>
317-
<xsl:with-param name="predicate" select="'?objectPredicate' || $suffix"/>
318-
<xsl:with-param name="object" select="'?objectObject' || $suffix"/>
319-
<xsl:with-param name="graph-var" select="'?objectGraph' || $suffix"/>
320-
</xsl:call-template>
321-
</json:array>
322-
</json:map>
353+
<json:string><xsl:value-of select="$object-var"/></json:string>
323354
</json:array>
324355
</json:map>
325-
</json:map>
326-
327-
<json:map>
328-
<json:string key="type">bind</json:string>
329-
<json:string key="variable"><xsl:value-of select="$inverse-var"/></json:string>
330-
<json:string key="expression">"false"^^&xsd;boolean</json:string>
331-
</json:map>
332-
</json:array>
356+
</json:array>
357+
</json:map>
333358
</json:map>
334359

335-
<!-- incoming: what points at the result set -->
336360
<json:map>
337-
<json:string key="type">group</json:string>
338-
<json:array key="patterns">
339-
<xsl:call-template name="ldh:GraphUnionPattern">
340-
<xsl:with-param name="subject" select="$subject-var"/>
341-
<xsl:with-param name="predicate" select="$predicate-var"/>
342-
<xsl:with-param name="object" select="$focus-var"/>
343-
<xsl:with-param name="graph-var" select="'?subjectGraph' || $suffix"/>
344-
</xsl:call-template>
345-
346-
<json:map>
347-
<json:string key="type">bind</json:string>
348-
<json:string key="variable"><xsl:value-of select="$inverse-var"/></json:string>
349-
<json:string key="expression">"true"^^&xsd;boolean</json:string>
350-
</json:map>
351-
</json:array>
361+
<json:string key="type">filter</json:string>
362+
<json:map key="expression">
363+
<json:string key="type">operation</json:string>
364+
<json:string key="operator">exists</json:string>
365+
<json:array key="args">
366+
<json:map>
367+
<json:string key="type">group</json:string>
368+
<json:array key="patterns">
369+
<xsl:call-template name="ldh:GraphUnionPattern">
370+
<xsl:with-param name="subject" select="$object-var"/>
371+
<xsl:with-param name="predicate" select="'?objectPredicate' || $suffix"/>
372+
<xsl:with-param name="object" select="'?objectObject' || $suffix"/>
373+
<xsl:with-param name="graph-var" select="'?objectGraph' || $suffix"/>
374+
</xsl:call-template>
375+
</json:array>
376+
</json:map>
377+
</json:array>
378+
</json:map>
352379
</json:map>
353-
</json:array>
380+
</xsl:otherwise>
381+
</xsl:choose>
382+
</json:array>
383+
<json:array key="group">
384+
<json:map>
385+
<json:string key="expression"><xsl:value-of select="$predicate-var"/></json:string>
354386
</json:map>
355387
</json:array>
356-
<json:number key="limit"><xsl:value-of select="$limit"/></json:number>
357388
</json:map>
358389
</xsl:template>
359390

0 commit comments

Comments
 (0)