Skip to content

Commit ad2af34

Browse files
committed
v0.3: JoinResolver Operationnal
The join resolver allow to call the autoJoin function of SelectQuery which add automatically all missing table in join clauses by finding the bests foreign keys. Fix bugs
1 parent 526cfeb commit ad2af34

File tree

8 files changed

+297
-39
lines changed

8 files changed

+297
-39
lines changed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ apply plugin: 'java'
22
apply plugin: 'eclipse'
33

44
sourceCompatibility = 1.6
5-
version = '0.2'
5+
version = '0.3'
66

77
manifest.mainAttributes(
88
'Implementation-Title': 'java-OracleSQL-dsl',
Lines changed: 261 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,284 @@
1+
/*
2+
*
3+
*/
14
package com.thibaultdelor.JSQL;
25

6+
import java.util.ArrayList;
7+
import java.util.Collection;
38
import java.util.HashSet;
49
import java.util.List;
10+
import java.util.Map.Entry;
511
import java.util.Set;
612

13+
import com.thibaultdelor.JSQL.criteria.BinaryCriterion;
14+
import com.thibaultdelor.JSQL.criteria.BinaryCriterion.BinaryOperator;
715
import com.thibaultdelor.JSQL.join.ExplicitJoin.JoinType;
816
import com.thibaultdelor.JSQL.join.JoinClause;
17+
import com.thibaultdelor.JSQL.join.OnJoin;
918

1019
/**
1120
* JoinResolver allows to find the best list of joins to add.
1221
*/
13-
@SuppressWarnings(value="all") //TODO yet Not Implemented
22+
@SuppressWarnings(value = "all")
1423
public class JoinResolver {
1524

16-
17-
private final Set<Table> mustJoinTables;
18-
private final Set<Table> existingTables;
19-
20-
private final int depth;
21-
25+
private final SelectQuery query;
26+
27+
private int depth;
28+
List<TableNode> nodes = new ArrayList<JoinResolver.TableNode>();
2229

23-
public JoinResolver(Set<Table> mustJoinTables, Set<Table> existingTables,
24-
int depth) {
30+
/**
31+
* Constructor of JoinResolver.
32+
*
33+
* @param query the query to complete
34+
* @param depth the maximum number of joins to add by table
35+
*/
36+
public JoinResolver(SelectQuery query, int depth) {
2537
super();
26-
this.mustJoinTables = new HashSet<Table>(mustJoinTables);
27-
this.existingTables = new HashSet<Table>(existingTables);
38+
this.query = query;
2839
this.depth = depth;
2940
}
41+
42+
public int getDepth() {
43+
return depth;
44+
}
45+
46+
public void setDepth(int depth) {
47+
this.depth = depth;
48+
}
49+
50+
/**
51+
* Auto add join clauses thanks to foreign keys.
52+
*
53+
* @param joinType the join type
54+
*/
55+
public void resolve(JoinType joinType) {
56+
Set<Table> existingTables = query.getExistingTables();
57+
buildNodes(existingTables);
58+
Set<Table> missingTables = query.getMissingTables();
59+
if (existingTables.size() == 0) {
60+
if (missingTables.size() == 0)
61+
throw new IllegalArgumentException(
62+
"No tables in query nor tables to join!?!");
63+
else {
64+
Table firstTable = missingTables.iterator().next();
65+
query.from(firstTable);
66+
existingTables.add(firstTable);
67+
missingTables.remove(firstTable);
68+
}
69+
}
70+
71+
for (Table table : missingTables) {
72+
TableNode bestNode = findNodeByTableName(table.getName());
73+
74+
if (bestNode == null || bestNode.depth < 0) {
75+
StringBuilder msg = new StringBuilder(
76+
"Cannot auto-resolve the table ");
77+
msg.append(table.getName());
78+
msg.append(" with existing tableNode : [");
79+
for (TableNode node : nodes) {
80+
msg.append(node.toString() + ",");
81+
}
82+
msg.deleteCharAt(msg.length() - 1);
83+
msg.append("]");
84+
throw new IllegalStateException(msg.toString());
85+
}
86+
includeNodeInQuery(bestNode, joinType);
87+
88+
}
89+
90+
}
91+
3092

93+
/**
94+
* Builds the nodes hierarchy thanks to foreign keys of existing tables.
95+
*
96+
* @param existingTables the existing tables in query
97+
*/
98+
private void buildNodes(Set<Table> existingTables) {
99+
nodes.clear();
100+
for (Table table : existingTables) {
101+
nodes.add(new TableNode(table, 0, null, null, true));
102+
}
103+
addJoinNodes(new ArrayList<JoinResolver.TableNode>(nodes));
104+
}
105+
106+
/**
107+
* Adds nodes in the nodes hierarchy.
108+
*
109+
* @param baseNodes nodes to explore
110+
*/
111+
private void addJoinNodes(Collection<TableNode> baseNodes) {
112+
ArrayList<TableNode> addedNode = new ArrayList<JoinResolver.TableNode>();
113+
for (TableNode tableNode : baseNodes) {
114+
if (tableNode.depth > depth)
115+
continue;
116+
117+
for (Entry<Column, Column> fk : tableNode.table.getForeignKeys()
118+
.entrySet()) {
119+
Table remotetable = fk.getValue().getTable();
120+
TableNode myNode = findNodeByTableName(remotetable.getName());
121+
if (myNode == null) {
122+
myNode = new TableNode(remotetable, tableNode.depth + 1, fk
123+
.getValue().getName(), tableNode, false);
124+
nodes.add(myNode);
125+
addedNode.add(myNode);
126+
} else {
127+
updateBestNode(myNode, tableNode);
128+
}
129+
}
130+
}
131+
if (addedNode.size() > 0)
132+
addJoinNodes(addedNode);
133+
134+
}
31135

32-
public List<JoinClause> resolve(JoinType joinType) {
33-
// TODO Auto-generated method stub
136+
/**
137+
* Update a node if needed with a a direct linked node.<br />
138+
* e.g. If we have found a best path for baseNode, this path can be bettre
139+
* too for myNode
140+
*
141+
* @param myNode
142+
* the node to update
143+
* @param baseNode
144+
* the origanal node
145+
*/
146+
private void updateBestNode(TableNode myNode, TableNode baseNode) {
147+
148+
// if passing by myNode is better
149+
if ((baseNode.depth + 1) < myNode.depth
150+
|| (baseNode.inQuery && baseNode.depth + 1 == myNode.depth)) {
151+
152+
// update myNode property to use this better path
153+
myNode.bestForeignKey = null;
154+
for (Entry<Column, Column> fk : myNode.table.getForeignKeys()
155+
.entrySet()) {
156+
if (fk.getValue().getTable().isSameTable(baseNode.table)) {
157+
myNode.bestForeignKey = fk.getValue().getName();
158+
break;
159+
}
160+
}
161+
if (myNode.bestForeignKey == null)
162+
throw new RuntimeException("NOLink betwen node! mynode :"
163+
+ myNode.toString() + " ; basenode : "
164+
+ baseNode.toString());
165+
myNode.depth = baseNode.depth + 1;
166+
myNode.closestNode = baseNode;
167+
168+
// recursively update childs
169+
for (Entry<Column, Column> fk : myNode.table.getForeignKeys()
170+
.entrySet()) {
171+
TableNode child = findNodeByTableName(fk.getValue().getTable()
172+
.getName());
173+
if (child != null)
174+
updateBestNode(child, myNode);
175+
}
176+
}
177+
}
178+
179+
/**
180+
* Include the table of the node in thequery with a join Clause.
181+
*
182+
* @param node the node to include in query
183+
* @param joinType the join type
184+
*/
185+
private void includeNodeInQuery(TableNode node, JoinType joinType) {
186+
if (node.inQuery)
187+
return;
188+
includeNodeInQuery(node.closestNode, joinType);
189+
190+
Column col = node.table.get(node.bestForeignKey);
191+
Column fk = node.table.getForeignColumn(node.bestForeignKey);
192+
BinaryCriterion criterion = new BinaryCriterion(col,
193+
BinaryOperator.EQUAL, fk);
194+
query.join(new OnJoin(node.table, criterion));
195+
node.inQuery = true;
196+
}
197+
198+
private TableNode findNodeByTableName(String tableName) {
199+
for (TableNode node : nodes) {
200+
if (node.table.getName().equals(tableName))
201+
return node;
202+
}
34203
return null;
35204
}
36-
37-
38-
39-
205+
206+
/**
207+
* Represent a table of the database with extra information allowing to find the best path to existing tables in the database
208+
*/
209+
private static class TableNode {
210+
211+
/** The base table. */
212+
Table table;
213+
214+
/** The number of link needed to access an existing table . */
215+
int depth;
216+
217+
/** The foreign key used to join tables. */
218+
String bestForeignKey;
219+
220+
/** The closest node. */
221+
TableNode closestNode;
222+
223+
/** inQuery is true if the table is already joined in the query. */
224+
boolean inQuery;
225+
226+
TableNode() {
227+
}
228+
229+
TableNode(Table table, int depth, String bestForeignKey,
230+
TableNode closestNode, boolean inQuery) {
231+
this.table = table;
232+
this.depth = depth;
233+
this.bestForeignKey = bestForeignKey;
234+
this.closestNode = closestNode;
235+
this.inQuery = inQuery;
236+
}
237+
238+
@Override
239+
public int hashCode() {
240+
final int prime = 31;
241+
int result = 1;
242+
result = prime
243+
* result
244+
+ ((table.getName() == null) ? 0 : table.getName()
245+
.hashCode());
246+
return result;
247+
}
248+
249+
@Override
250+
public boolean equals(Object obj) {
251+
if (this == obj)
252+
return true;
253+
if (obj == null)
254+
return false;
255+
if (getClass() != obj.getClass())
256+
return false;
257+
TableNode other = (TableNode) obj;
258+
if (table.getName() == null) {
259+
if (other.table.getName() != null)
260+
return false;
261+
} else if (!table.getName().equals(other.table.getName()))
262+
return false;
263+
return true;
264+
}
265+
266+
@Override
267+
public String toString() {
268+
StringBuilder builder = new StringBuilder();
269+
builder.append("TableNode [table=");
270+
builder.append(table.getName());
271+
builder.append(", depth=");
272+
builder.append(depth);
273+
builder.append(", bestForeignKey=");
274+
builder.append(bestForeignKey);
275+
builder.append(", inQuery=");
276+
builder.append(inQuery);
277+
builder.append("]");
278+
return builder.toString();
279+
}
280+
281+
282+
}
283+
40284
}

src/main/java/com/thibaultdelor/JSQL/SelectQuery.java

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,15 @@ public class SelectQuery {
2727
private final List<JoinClause> join = new ArrayList<JoinClause>();
2828
private final LogicalExpression where = new LogicalExpression("", "\nAND ",
2929
"");
30-
private final List<Column> groupBy = new ArrayList<Column>();
30+
private final List<SQLOutputable> groupBy = new ArrayList<SQLOutputable>();
3131
private final List<Criterion> having = new ArrayList<Criterion>();
3232
private final List<OrderClause> orderBy = new ArrayList<OrderClause>();
3333

3434
private final Set<Table> allReferencedTables = new HashSet<Table>(4);
35+
36+
37+
/** The join resolver use for auto join (lazy initialized). */
38+
private JoinResolver joinResolver = null;
3539

3640
public SelectQuery select(SQLOutputable... cols) {
3741
for (SQLOutputable c : cols) {
@@ -97,9 +101,9 @@ public SelectQuery whereIn(Column userId, String... values) {
97101
return where(new InCriterion(userId, new LiteralSet(values)));
98102
}
99103

100-
public SelectQuery groupBy(Column c) {
101-
if (groupBy.add(c))
102-
c.addNeededTables(allReferencedTables);
104+
public SelectQuery groupBy(SQLOutputable groupCol) {
105+
if (groupBy.add(groupCol))
106+
groupCol.addNeededTables(allReferencedTables);
103107
return this;
104108

105109
}
@@ -167,28 +171,27 @@ public void autoJoin() {
167171
* the maximum depth of foreign key relation between tables, 0 for unlimited
168172
*/
169173
public void autoJoin(JoinType joinType, int depth) {
170-
if(depth<1)
174+
if(depth<0)
171175
throw new IllegalArgumentException("depth mustn't be negative");
172176
if(depth==0)
173177
depth = Integer.MAX_VALUE;
174-
Set<Table> missingTables = getMissingTables();
175-
Set<Table> existingTables = getExistingTables();
176-
JoinResolver joinResolver = new JoinResolver(missingTables, existingTables, depth);
177-
List<JoinClause> joinClauses = joinResolver.resolve(joinType);
178-
179-
for (JoinClause jc : joinClauses) {
180-
join(jc);
178+
if(joinResolver == null){
179+
joinResolver = new JoinResolver(this, depth);
180+
}
181+
else {
182+
joinResolver.setDepth(depth);
181183
}
184+
joinResolver.resolve(joinType);
182185
}
183186

184-
private Set<Table> getExistingTables() {
187+
Set<Table> getExistingTables() {
185188
Set<Table> joinedTables = new HashSet<Table>(from);
186189
for (JoinClause jc : join) {
187190
joinedTables.add(jc.getJoinTable());
188191
}
189192
return joinedTables;
190193
}
191-
private Set<Table> getMissingTables() {
194+
Set<Table> getMissingTables() {
192195
Set<Table> missingTables = new HashSet<Table>(allReferencedTables);
193196
missingTables.removeAll(from);
194197
Set<Table> joinedTables = new HashSet<Table>(4);

0 commit comments

Comments
 (0)