Skip to content

Commit 87336c5

Browse files
committed
GH-4998: Add JoinQuery for easy and efficient creation of joins in rdf4j-spring DAOs
1 parent ac2accd commit 87336c5

File tree

9 files changed

+677
-0
lines changed

9 files changed

+677
-0
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* *****************************************************************************
3+
* Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others.
4+
* All rights reserved. This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Distribution License v1.0
6+
* which accompanies this distribution, and is available at
7+
* http://www.eclipse.org/org/documents/edl-v10.php.
8+
*
9+
* SPDX-License-Identifier: BSD-3-Clause
10+
* *****************************************************************************
11+
*/
12+
13+
package org.eclipse.rdf4j.spring.dao.support.join;
14+
15+
import java.util.function.Supplier;
16+
17+
import org.eclipse.rdf4j.sparqlbuilder.core.SparqlBuilder;
18+
import org.eclipse.rdf4j.sparqlbuilder.core.Variable;
19+
import org.eclipse.rdf4j.spring.support.RDF4JTemplate;
20+
21+
/**
22+
* Creates a reusable {@link org.eclipse.rdf4j.query.TupleQuery} (and takes care of it getting reused properly using
23+
* {@link RDF4JTemplate#tupleQuery(Class, String, Supplier)}).
24+
*
25+
* <p>
26+
* The JoinQuery is created using the {@link JoinQueryBuilder}.
27+
*
28+
* <p>
29+
* To set bindings and execute a {@link JoinQuery}, obtain the {@link JoinQueryEvaluationBuilder} via
30+
* {@link #evaluationBuilder(RDF4JTemplate)}.
31+
*/
32+
public class JoinQuery {
33+
34+
public static final Variable _sourceEntity = SparqlBuilder.var("sourceEntity");
35+
public static final Variable _targetEntity = SparqlBuilder.var("targetEntity");
36+
37+
private final String queryString;
38+
39+
JoinQuery(JoinQueryBuilder joinQueryBuilder) {
40+
this.queryString = joinQueryBuilder.makeQueryString();
41+
}
42+
43+
public JoinQueryEvaluationBuilder evaluationBuilder(RDF4JTemplate rdf4JTemplate) {
44+
return new JoinQueryEvaluationBuilder(
45+
rdf4JTemplate.tupleQuery(
46+
getClass(),
47+
this.getClass().getName() + "@" + this.hashCode(),
48+
() -> this.queryString));
49+
}
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
/*
2+
* *****************************************************************************
3+
* Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others.
4+
* All rights reserved. This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Distribution License v1.0
6+
* which accompanies this distribution, and is available at
7+
* http://www.eclipse.org/org/documents/edl-v10.php.
8+
*
9+
* SPDX-License-Identifier: BSD-3-Clause
10+
* *****************************************************************************
11+
*/
12+
package org.eclipse.rdf4j.spring.dao.support.join;
13+
14+
import static org.eclipse.rdf4j.sparqlbuilder.rdf.Rdf.iri;
15+
16+
import java.util.function.Function;
17+
18+
import org.eclipse.rdf4j.model.IRI;
19+
import org.eclipse.rdf4j.sparqlbuilder.constraint.propertypath.builder.PropertyPathBuilder;
20+
import org.eclipse.rdf4j.sparqlbuilder.core.Projectable;
21+
import org.eclipse.rdf4j.sparqlbuilder.core.Variable;
22+
import org.eclipse.rdf4j.sparqlbuilder.core.query.Queries;
23+
import org.eclipse.rdf4j.sparqlbuilder.graphpattern.GraphPattern;
24+
import org.eclipse.rdf4j.sparqlbuilder.rdf.RdfPredicate;
25+
import org.eclipse.rdf4j.spring.support.RDF4JTemplate;
26+
27+
/**
28+
* Builder for the {@link JoinQuery}. Allows for building the JoinQuery object directly via
29+
* {@link #build(RDF4JTemplate)}, and for building a lazy initizalizer via {@link #buildLazyInitializer()}.
30+
*
31+
* <p>
32+
* You would use the lazy initializer like so:
33+
*
34+
* <pre>
35+
* public class MyDao extends RDF4JDAO {
36+
* // ...
37+
*
38+
* private static final LazyJoinQueryInitizalizer lazyJoinQuery = JoinQueryBuilder.of(SKOS.broader)
39+
* // .. configure your join
40+
* .buildLazyInitializer();
41+
*
42+
* public Map<IRI, Set<IRI>> getJoinedData(IRI sourceEntityId) {
43+
* return lazyJoinQuery.get(getRdf4JTemplate())
44+
* .withSourceEntityIdBinding(sourceEntityId)
45+
* .buildOneToMany();
46+
* }
47+
*
48+
* }
49+
*
50+
* </pre>
51+
*/
52+
public class JoinQueryBuilder {
53+
54+
private final RdfPredicate predicate;
55+
private GraphPattern subjectConstraints = null;
56+
private GraphPattern objectConstraints = null;
57+
private JoinType joinType = JoinType.INNER;
58+
59+
private JoinQueryBuilder(RdfPredicate predicate) {
60+
this.predicate = predicate;
61+
}
62+
63+
private JoinQueryBuilder(IRI predicate) {
64+
this.predicate = iri(predicate);
65+
}
66+
67+
public static JoinQueryBuilder of(RdfPredicate rdfPredicate) {
68+
return new JoinQueryBuilder(rdfPredicate);
69+
}
70+
71+
public static JoinQueryBuilder of(IRI predicate) {
72+
return new JoinQueryBuilder(predicate);
73+
}
74+
75+
public static JoinQueryBuilder of(
76+
RDF4JTemplate rdf4JTemplate, PropertyPathBuilder propertyPathBuilder) {
77+
return new JoinQueryBuilder(() -> propertyPathBuilder.build().getQueryString());
78+
}
79+
80+
public JoinQueryBuilder sourceEntityConstraints(
81+
Function<Variable, GraphPattern> constraintBuilder) {
82+
this.subjectConstraints = constraintBuilder.apply(JoinQuery._sourceEntity);
83+
return this;
84+
}
85+
86+
public JoinQueryBuilder targetEntityConstraints(
87+
Function<Variable, GraphPattern> constraintBuilder) {
88+
this.objectConstraints = constraintBuilder.apply(JoinQuery._targetEntity);
89+
return this;
90+
}
91+
92+
/**
93+
* Return only results where the relation is present and subjectConstraints and objectConstraints are satisfied.
94+
*
95+
* @return
96+
*/
97+
public JoinQueryBuilder innerJoin() {
98+
this.joinType = JoinType.INNER;
99+
return this;
100+
}
101+
102+
/**
103+
* Return results where subjectConstraints are satisfied. The existence of the relation is optional, but
104+
* objectConstraints must be satisfied where the relation exists.
105+
*
106+
* @return
107+
*/
108+
public JoinQueryBuilder leftOuterJoin() {
109+
this.joinType = JoinType.LEFT_OUTER;
110+
return this;
111+
}
112+
113+
/**
114+
* Return results where objectConstraints are satisfied, The existence of the relation is optional, and
115+
* subjectConstraints are satisfied where the relation exists.
116+
*
117+
* @return
118+
*/
119+
public JoinQueryBuilder rightOuterJoin() {
120+
this.joinType = JoinType.RIGHT_OUTER;
121+
return this;
122+
}
123+
124+
public JoinQuery build() {
125+
return new JoinQuery(this);
126+
}
127+
128+
public LazyJoinQueryInitizalizer buildLazyInitializer() {
129+
return new LazyJoinQueryInitizalizer(this);
130+
}
131+
132+
String makeQueryString() {
133+
return Queries.SELECT(getProjection()).where(getWhereClause()).distinct().getQueryString();
134+
}
135+
136+
private Projectable[] getProjection() {
137+
return new Projectable[] { JoinQuery._sourceEntity, JoinQuery._targetEntity };
138+
}
139+
140+
private GraphPattern andIfPresent(GraphPattern leftOrNull, GraphPattern rightOrNull) {
141+
if (rightOrNull == null) {
142+
return leftOrNull;
143+
}
144+
if (leftOrNull == null) {
145+
if (rightOrNull == null) {
146+
throw new UnsupportedOperationException("left or right parameter must be non-null");
147+
}
148+
return rightOrNull;
149+
}
150+
return leftOrNull.and(rightOrNull);
151+
}
152+
153+
private GraphPattern optionalIfPresent(GraphPattern patternOrNull) {
154+
if (patternOrNull == null) {
155+
return null;
156+
}
157+
return patternOrNull.optional();
158+
}
159+
160+
private GraphPattern getWhereClause() {
161+
GraphPattern relation = JoinQuery._sourceEntity.has(predicate, JoinQuery._targetEntity);
162+
switch (this.joinType) {
163+
case INNER:
164+
return andIfPresent(
165+
andIfPresent(relation, this.subjectConstraints), this.objectConstraints);
166+
case LEFT_OUTER:
167+
return andIfPresent(
168+
this.subjectConstraints,
169+
andIfPresent(relation, this.objectConstraints).optional());
170+
case RIGHT_OUTER:
171+
return andIfPresent(
172+
this.objectConstraints,
173+
andIfPresent(relation, this.subjectConstraints).optional());
174+
}
175+
throw new UnsupportedOperationException("Join type Not supported: " + this.joinType);
176+
}
177+
}

0 commit comments

Comments
 (0)