17 |
ilm |
1 |
/*
|
|
|
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
|
|
|
3 |
*
|
182 |
ilm |
4 |
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
|
17 |
ilm |
5 |
*
|
|
|
6 |
* The contents of this file are subject to the terms of the GNU General Public License Version 3
|
|
|
7 |
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
|
|
|
8 |
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
|
|
|
9 |
* language governing permissions and limitations under the License.
|
|
|
10 |
*
|
|
|
11 |
* When distributing the software, include this License Header Notice in each file.
|
|
|
12 |
*/
|
|
|
13 |
|
|
|
14 |
package org.openconcerto.sql.model;
|
|
|
15 |
|
80 |
ilm |
16 |
import org.openconcerto.sql.model.SQLRowValues.CreateMode;
|
17 |
ilm |
17 |
import org.openconcerto.sql.model.SQLRowValuesCluster.State;
|
83 |
ilm |
18 |
import org.openconcerto.sql.model.SQLRowValuesCluster.WalkOptions;
|
67 |
ilm |
19 |
import org.openconcerto.sql.model.graph.Link.Direction;
|
17 |
ilm |
20 |
import org.openconcerto.sql.model.graph.Path;
|
|
|
21 |
import org.openconcerto.sql.model.graph.Step;
|
83 |
ilm |
22 |
import org.openconcerto.utils.CollectionMap2Itf.ListMapItf;
|
132 |
ilm |
23 |
import org.openconcerto.utils.CollectionUtils;
|
17 |
ilm |
24 |
import org.openconcerto.utils.CompareUtils;
|
93 |
ilm |
25 |
import org.openconcerto.utils.CopyUtils;
|
80 |
ilm |
26 |
import org.openconcerto.utils.ListMap;
|
17 |
ilm |
27 |
import org.openconcerto.utils.RTInterruptedException;
|
|
|
28 |
import org.openconcerto.utils.RecursionType;
|
151 |
ilm |
29 |
import org.openconcerto.utils.SetMap;
|
17 |
ilm |
30 |
import org.openconcerto.utils.Tuple2;
|
|
|
31 |
import org.openconcerto.utils.cc.ITransformer;
|
144 |
ilm |
32 |
import org.openconcerto.utils.cc.LinkedIdentitySet;
|
142 |
ilm |
33 |
import org.openconcerto.utils.cc.Transformer;
|
17 |
ilm |
34 |
|
|
|
35 |
import java.sql.ResultSet;
|
|
|
36 |
import java.sql.SQLException;
|
|
|
37 |
import java.util.ArrayList;
|
|
|
38 |
import java.util.Collection;
|
|
|
39 |
import java.util.Collections;
|
144 |
ilm |
40 |
import java.util.Deque;
|
17 |
ilm |
41 |
import java.util.HashMap;
|
|
|
42 |
import java.util.HashSet;
|
80 |
ilm |
43 |
import java.util.LinkedHashMap;
|
73 |
ilm |
44 |
import java.util.LinkedHashSet;
|
144 |
ilm |
45 |
import java.util.LinkedList;
|
17 |
ilm |
46 |
import java.util.List;
|
|
|
47 |
import java.util.Map;
|
|
|
48 |
import java.util.Map.Entry;
|
|
|
49 |
import java.util.Set;
|
|
|
50 |
import java.util.concurrent.Callable;
|
|
|
51 |
import java.util.concurrent.ExecutorService;
|
|
|
52 |
import java.util.concurrent.Future;
|
|
|
53 |
import java.util.concurrent.LinkedBlockingQueue;
|
|
|
54 |
import java.util.concurrent.ThreadPoolExecutor;
|
|
|
55 |
import java.util.concurrent.TimeUnit;
|
83 |
ilm |
56 |
import java.util.concurrent.atomic.AtomicInteger;
|
80 |
ilm |
57 |
import java.util.concurrent.atomic.AtomicReference;
|
17 |
ilm |
58 |
|
132 |
ilm |
59 |
import org.apache.commons.dbutils.ResultSetHandler;
|
|
|
60 |
|
93 |
ilm |
61 |
import net.jcip.annotations.GuardedBy;
|
|
|
62 |
import net.jcip.annotations.ThreadSafe;
|
|
|
63 |
|
17 |
ilm |
64 |
/**
|
|
|
65 |
* Construct a list of linked SQLRowValues from one request.
|
|
|
66 |
*
|
|
|
67 |
* @author Sylvain
|
|
|
68 |
*/
|
93 |
ilm |
69 |
@ThreadSafe
|
17 |
ilm |
70 |
public class SQLRowValuesListFetcher {
|
|
|
71 |
|
61 |
ilm |
72 |
/**
|
65 |
ilm |
73 |
* Create an ordered fetcher with the necessary grafts to fetch the passed graph.
|
61 |
ilm |
74 |
*
|
|
|
75 |
* @param graph what to fetch, can be any tree.
|
|
|
76 |
* @return the fetcher.
|
|
|
77 |
*/
|
|
|
78 |
public static SQLRowValuesListFetcher create(final SQLRowValues graph) {
|
65 |
ilm |
79 |
// ORDER shouldn't slow down the query and it makes the result predictable and repeatable
|
|
|
80 |
return create(graph, true);
|
|
|
81 |
}
|
|
|
82 |
|
|
|
83 |
public static SQLRowValuesListFetcher create(final SQLRowValues graph, final boolean ordered) {
|
61 |
ilm |
84 |
// path -> longest referent only path
|
|
|
85 |
// i.e. map each path to the main fetcher or a referent graft
|
|
|
86 |
final Map<Path, Path> handledPaths = new HashMap<Path, Path>();
|
80 |
ilm |
87 |
final Path emptyPath = Path.get(graph.getTable());
|
61 |
ilm |
88 |
handledPaths.put(emptyPath, emptyPath);
|
|
|
89 |
// find out referent only paths (yellow in the diagram)
|
|
|
90 |
graph.getGraph().walk(graph, null, new ITransformer<State<Object>, Object>() {
|
|
|
91 |
@Override
|
|
|
92 |
public Path transformChecked(State<Object> input) {
|
|
|
93 |
final Path p = input.getPath();
|
|
|
94 |
for (int i = p.length(); i > 0; i--) {
|
|
|
95 |
final Path subPath = p.subPath(0, i);
|
|
|
96 |
if (handledPaths.containsKey(subPath))
|
|
|
97 |
break;
|
|
|
98 |
handledPaths.put(subPath, p);
|
|
|
99 |
}
|
|
|
100 |
return null;
|
|
|
101 |
}
|
80 |
ilm |
102 |
}, RecursionType.DEPTH_FIRST, Direction.REFERENT);
|
61 |
ilm |
103 |
|
|
|
104 |
// find out needed grafts
|
83 |
ilm |
105 |
final ListMap<Path, SQLRowValuesListFetcher> grafts = new ListMap<Path, SQLRowValuesListFetcher>();
|
61 |
ilm |
106 |
graph.getGraph().walk(graph, null, new ITransformer<State<Object>, Object>() {
|
|
|
107 |
@Override
|
|
|
108 |
public Path transformChecked(State<Object> input) {
|
|
|
109 |
final Path p = input.getPath();
|
|
|
110 |
if (!handledPaths.containsKey(p)) {
|
|
|
111 |
final Path pMinusLast = p.minusLast();
|
|
|
112 |
if (!input.isBackwards()) {
|
|
|
113 |
// Forwards can be fetched by existing fetcher (blue in the diagram)
|
|
|
114 |
final Path existingRefPath = handledPaths.get(pMinusLast);
|
|
|
115 |
assert existingRefPath != null;
|
|
|
116 |
handledPaths.put(p, existingRefPath);
|
|
|
117 |
} else {
|
|
|
118 |
// Backwards needs another fetcher
|
|
|
119 |
if (!grafts.containsKey(pMinusLast)) {
|
|
|
120 |
final SQLRowValues copy = graph.deepCopy();
|
|
|
121 |
final SQLRowValues graftNode = copy.followPath(pMinusLast);
|
|
|
122 |
graftNode.clear();
|
|
|
123 |
final SQLRowValues previous = copy.followPath(pMinusLast.minusLast());
|
|
|
124 |
assert p.getStep(-2).isForeign();
|
|
|
125 |
previous.remove(p.getStep(-2).getSingleField().getName());
|
|
|
126 |
// don't recurse forever
|
|
|
127 |
if (previous.getGraph() == graftNode.getGraph())
|
|
|
128 |
throw new IllegalArgumentException("Graph is not a tree");
|
65 |
ilm |
129 |
// ATTN pMinusLast might not be on the main fetcher so don't graft now
|
|
|
130 |
// also we can only graft non empty descendant path fetchers (plus
|
|
|
131 |
// removing a fetcher saves one request)
|
|
|
132 |
final SQLRowValuesListFetcher rec = create(graftNode, ordered);
|
|
|
133 |
final Collection<SQLRowValuesListFetcher> ungrafted = rec.ungraft();
|
|
|
134 |
if (ungrafted == null || ungrafted.size() == 0) {
|
|
|
135 |
// i.e. only one referent and thus graft not necessary
|
|
|
136 |
assert rec.descendantPath.length() > 0;
|
83 |
ilm |
137 |
grafts.add(pMinusLast, rec);
|
65 |
ilm |
138 |
} else {
|
83 |
ilm |
139 |
grafts.addAll(pMinusLast, ungrafted);
|
65 |
ilm |
140 |
}
|
61 |
ilm |
141 |
}
|
|
|
142 |
throw new SQLRowValuesCluster.StopRecurseException().setCompletely(false);
|
|
|
143 |
}
|
|
|
144 |
}
|
|
|
145 |
return null;
|
|
|
146 |
}
|
83 |
ilm |
147 |
}, new WalkOptions(Direction.ANY).setRecursionType(RecursionType.BREADTH_FIRST).setStartIncluded(false));
|
61 |
ilm |
148 |
|
|
|
149 |
final Set<Path> refPaths = new HashSet<Path>(handledPaths.values());
|
|
|
150 |
// remove the main fetcher
|
|
|
151 |
refPaths.remove(emptyPath);
|
65 |
ilm |
152 |
// fetchers for the referent paths (yellow part)
|
61 |
ilm |
153 |
final Map<Path, SQLRowValuesListFetcher> graftedFetchers;
|
|
|
154 |
// create the main fetcher and grafts
|
|
|
155 |
final SQLRowValuesListFetcher res;
|
|
|
156 |
if (refPaths.size() == 1) {
|
|
|
157 |
res = new SQLRowValuesListFetcher(graph, refPaths.iterator().next());
|
|
|
158 |
graftedFetchers = Collections.emptyMap();
|
|
|
159 |
} else {
|
|
|
160 |
res = new SQLRowValuesListFetcher(graph, false);
|
|
|
161 |
graftedFetchers = new HashMap<Path, SQLRowValuesListFetcher>();
|
|
|
162 |
if (refPaths.size() > 0) {
|
|
|
163 |
final Path graftPath = new Path(graph.getTable());
|
|
|
164 |
final SQLRowValues copy = graph.deepCopy();
|
|
|
165 |
copy.clear();
|
|
|
166 |
for (final Path refPath : refPaths) {
|
65 |
ilm |
167 |
final SQLRowValuesListFetcher f = new SQLRowValuesListFetcher(copy, refPath, true).setOrdered(ordered);
|
61 |
ilm |
168 |
res.graft(f, graftPath);
|
|
|
169 |
graftedFetchers.put(refPath, f);
|
|
|
170 |
}
|
|
|
171 |
}
|
|
|
172 |
}
|
65 |
ilm |
173 |
res.setOrdered(ordered);
|
|
|
174 |
|
61 |
ilm |
175 |
// now graft recursively created grafts
|
83 |
ilm |
176 |
for (final Entry<Path, ? extends Collection<SQLRowValuesListFetcher>> e : grafts.entrySet()) {
|
61 |
ilm |
177 |
final Path graftPath = e.getKey();
|
|
|
178 |
final Path refPath = handledPaths.get(graftPath);
|
65 |
ilm |
179 |
// can be grafted on the main fetcher or on the referent fetchers
|
61 |
ilm |
180 |
final SQLRowValuesListFetcher f = graftedFetchers.containsKey(refPath) ? graftedFetchers.get(refPath) : res;
|
65 |
ilm |
181 |
for (final SQLRowValuesListFetcher recFetcher : e.getValue())
|
|
|
182 |
f.graft(recFetcher, graftPath);
|
61 |
ilm |
183 |
}
|
|
|
184 |
return res;
|
|
|
185 |
}
|
|
|
186 |
|
17 |
ilm |
187 |
// return the referent single link path starting from graph
|
|
|
188 |
private static Path computePath(SQLRowValues graph) {
|
|
|
189 |
// check that there's only one referent for each row
|
|
|
190 |
// (otherwise huge joins, e.g. LOCAL<-CPI,SOURCE,RECEPTEUR,etc.)
|
80 |
ilm |
191 |
final AtomicReference<Path> res = new AtomicReference<Path>(null);
|
|
|
192 |
graph.getGraph().walk(graph, null, new ITransformer<State<Path>, Path>() {
|
17 |
ilm |
193 |
@Override
|
|
|
194 |
public Path transformChecked(State<Path> input) {
|
|
|
195 |
final Collection<SQLRowValues> referentRows = input.getCurrent().getReferentRows();
|
80 |
ilm |
196 |
final int size = referentRows.size();
|
|
|
197 |
if (size > 1) {
|
17 |
ilm |
198 |
// remove the foreign rows which are all the same (since they point to
|
|
|
199 |
// current) so the exn is more legible
|
|
|
200 |
final List<SQLRowValues> toPrint = SQLRowValues.trim(referentRows);
|
|
|
201 |
throw new IllegalArgumentException(input.getCurrent() + " is referenced by " + toPrint + "\nat " + input.getPath());
|
80 |
ilm |
202 |
} else if (size == 0) {
|
|
|
203 |
if (res.get() == null)
|
|
|
204 |
res.set(input.getPath());
|
|
|
205 |
else
|
|
|
206 |
throw new IllegalStateException();
|
17 |
ilm |
207 |
}
|
|
|
208 |
return input.getAcc();
|
|
|
209 |
}
|
83 |
ilm |
210 |
}, RecursionType.BREADTH_FIRST, Direction.REFERENT);
|
80 |
ilm |
211 |
// since includeStart=true
|
|
|
212 |
assert res.get() != null;
|
|
|
213 |
return res.get();
|
17 |
ilm |
214 |
}
|
|
|
215 |
|
83 |
ilm |
216 |
static private final ListMap<Tuple2<Path, Number>, SQLRowValues> createCollectionMap() {
|
17 |
ilm |
217 |
// we need a List in merge()
|
83 |
ilm |
218 |
return new ListMap<Tuple2<Path, Number>, SQLRowValues>() {
|
|
|
219 |
@Override
|
|
|
220 |
public List<SQLRowValues> createCollection(Collection<? extends SQLRowValues> v) {
|
|
|
221 |
final List<SQLRowValues> res = new ArrayList<SQLRowValues>(8);
|
|
|
222 |
res.addAll(v);
|
|
|
223 |
return res;
|
|
|
224 |
}
|
|
|
225 |
};
|
17 |
ilm |
226 |
}
|
|
|
227 |
|
93 |
ilm |
228 |
// unmodifiable
|
17 |
ilm |
229 |
private final SQLRowValues graph;
|
|
|
230 |
private final Path descendantPath;
|
93 |
ilm |
231 |
@GuardedBy("this")
|
132 |
ilm |
232 |
private List<ITransformer<SQLSelect, SQLSelect>> selTransf;
|
93 |
ilm |
233 |
@GuardedBy("this")
|
|
|
234 |
private Number selID;
|
|
|
235 |
@GuardedBy("this")
|
73 |
ilm |
236 |
private Set<Path> ordered;
|
93 |
ilm |
237 |
@GuardedBy("this")
|
73 |
ilm |
238 |
private boolean descendantsOrdered;
|
93 |
ilm |
239 |
@GuardedBy("this")
|
17 |
ilm |
240 |
private SQLRowValues minGraph;
|
93 |
ilm |
241 |
@GuardedBy("this")
|
17 |
ilm |
242 |
private boolean includeForeignUndef;
|
93 |
ilm |
243 |
@GuardedBy("this")
|
17 |
ilm |
244 |
private SQLSelect frozen;
|
93 |
ilm |
245 |
@GuardedBy("this")
|
|
|
246 |
private boolean freezeRows;
|
|
|
247 |
// graftPlace -> {referent path -> fetcher}, unmodifiable
|
|
|
248 |
@GuardedBy("this")
|
|
|
249 |
private Map<Path, Map<Path, SQLRowValuesListFetcher>> grafts;
|
151 |
ilm |
250 |
// {pathToAdd, existingPath}, unmodifiable
|
|
|
251 |
@GuardedBy("this")
|
|
|
252 |
private Map<Path, Path> postFetchLinks;
|
17 |
ilm |
253 |
|
|
|
254 |
/**
|
|
|
255 |
* Construct a new instance with the passed graph of SQLRowValues.
|
|
|
256 |
*
|
|
|
257 |
* @param graph what SQLRowValues should be returned by {@link #fetch()}, eg <code>new
|
|
|
258 |
* SQLRowValues("SITE").setAllToNull().put("ID_CONTACT", new SQLRowValues("CONTACT"))</code>
|
|
|
259 |
* to return all sites (with all their fields) with their associated contacts.
|
|
|
260 |
*/
|
|
|
261 |
public SQLRowValuesListFetcher(SQLRowValues graph) {
|
|
|
262 |
this(graph, false);
|
|
|
263 |
}
|
|
|
264 |
|
|
|
265 |
/**
|
|
|
266 |
* Construct a new instance with the passed graph of SQLRowValues. Eg if <code>graph</code> is a
|
|
|
267 |
* BATIMENT which points to SITE, is pointed by LOCAL, CPI_BT and <code>referents</code> is
|
|
|
268 |
* <code>true</code>, {@link #fetch()} could return
|
|
|
269 |
*
|
|
|
270 |
* <pre>
|
|
|
271 |
* SITE[2] BATIMENT[2] LOCAL[2] CPI_BT[3]
|
|
|
272 |
* CPI_BT[2]
|
|
|
273 |
* LOCAL[3]
|
|
|
274 |
* LOCAL[5] CPI_BT[5]
|
|
|
275 |
* SITE[7] BATIMENT[3] LOCAL[4] CPI_BT[4]
|
|
|
276 |
* SITE[7] BATIMENT[4]
|
|
|
277 |
* </pre>
|
|
|
278 |
*
|
|
|
279 |
* @param graph what SQLRowValues should be returned by {@link #fetch()}, eg <code>new
|
|
|
280 |
* SQLRowValues("SITE").setAllToNull().put("ID_CONTACT", new SQLRowValues("CONTACT"))</code>
|
|
|
281 |
* to return all sites (with all their fields) with their associated contacts.
|
|
|
282 |
* @param referents <code>true</code> if referents to <code>graph</code> should also be fetched.
|
|
|
283 |
*/
|
|
|
284 |
public SQLRowValuesListFetcher(SQLRowValues graph, final boolean referents) {
|
|
|
285 |
this(graph, referents ? computePath(graph) : null);
|
|
|
286 |
}
|
|
|
287 |
|
|
|
288 |
/**
|
|
|
289 |
* Construct a new instance.
|
|
|
290 |
*
|
61 |
ilm |
291 |
* @param graph what SQLRowValues should be returned by {@link #fetch()}.
|
93 |
ilm |
292 |
* @param referentPath a {@link Path#isSingleField() single link} path from the primary table,
|
17 |
ilm |
293 |
* <code>null</code> meaning don't fetch referent rows.
|
|
|
294 |
*/
|
|
|
295 |
public SQLRowValuesListFetcher(SQLRowValues graph, final Path referentPath) {
|
61 |
ilm |
296 |
this(graph, referentPath, true);
|
|
|
297 |
}
|
|
|
298 |
|
|
|
299 |
/**
|
|
|
300 |
* Construct a new instance.
|
|
|
301 |
*
|
|
|
302 |
* @param graph what SQLRowValues should be returned by {@link #fetch()}.
|
93 |
ilm |
303 |
* @param referentPath a {@link Path#isSingleField() single link} path from the primary table,
|
61 |
ilm |
304 |
* <code>null</code> meaning don't fetch referent rows.
|
|
|
305 |
* @param prune if <code>true</code> the graph will be pruned to only contain
|
|
|
306 |
* <code>referentPath</code>. If <code>false</code> the graph will be kept as is, which
|
|
|
307 |
* can produce undefined results if there exist more than one referent row outside of
|
|
|
308 |
* <code>referentPath</code>.
|
|
|
309 |
*/
|
|
|
310 |
SQLRowValuesListFetcher(final SQLRowValues graph, final Path referentPath, final boolean prune) {
|
17 |
ilm |
311 |
super();
|
|
|
312 |
this.graph = graph.deepCopy();
|
93 |
ilm |
313 |
|
80 |
ilm |
314 |
this.descendantPath = referentPath == null ? Path.get(graph.getTable()) : referentPath;
|
67 |
ilm |
315 |
if (!this.descendantPath.isDirection(Direction.REFERENT))
|
|
|
316 |
throw new IllegalArgumentException("path is not (exclusively) referent : " + this.descendantPath);
|
61 |
ilm |
317 |
final SQLRowValues descRow = this.graph.followPath(this.descendantPath);
|
|
|
318 |
if (descRow == null)
|
|
|
319 |
throw new IllegalArgumentException("path is not contained in the passed rowValues : " + referentPath + "\n" + this.graph.printTree());
|
17 |
ilm |
320 |
// followPath() do the following check
|
93 |
ilm |
321 |
assert this.descendantPath.getFirst() == this.graph.getTable() && this.descendantPath.isSingleField();
|
61 |
ilm |
322 |
|
|
|
323 |
if (prune) {
|
|
|
324 |
this.graph.getGraph().walk(descRow, null, new ITransformer<State<Object>, Object>() {
|
|
|
325 |
@Override
|
|
|
326 |
public Object transformChecked(State<Object> input) {
|
|
|
327 |
if (input.getFrom() == null) {
|
|
|
328 |
input.getCurrent().clearReferents();
|
|
|
329 |
} else {
|
65 |
ilm |
330 |
input.getCurrent().retainReferent(input.getPrevious());
|
61 |
ilm |
331 |
}
|
|
|
332 |
return null;
|
|
|
333 |
}
|
80 |
ilm |
334 |
}, RecursionType.BREADTH_FIRST, Direction.FOREIGN);
|
61 |
ilm |
335 |
}
|
|
|
336 |
|
17 |
ilm |
337 |
// always need IDs
|
93 |
ilm |
338 |
for (final SQLRowValues curr : this.graph.getGraph().getItems()) {
|
17 |
ilm |
339 |
// don't overwrite existing values
|
|
|
340 |
if (!curr.hasID())
|
|
|
341 |
curr.setID(null);
|
|
|
342 |
}
|
|
|
343 |
|
93 |
ilm |
344 |
this.graph.getGraph().freeze();
|
|
|
345 |
|
|
|
346 |
synchronized (this) {
|
132 |
ilm |
347 |
this.selTransf = Collections.emptyList();
|
93 |
ilm |
348 |
this.selID = null;
|
|
|
349 |
this.ordered = Collections.<Path> emptySet();
|
|
|
350 |
this.descendantsOrdered = false;
|
|
|
351 |
this.minGraph = null;
|
|
|
352 |
this.includeForeignUndef = false;
|
|
|
353 |
this.frozen = null;
|
|
|
354 |
this.freezeRows = false;
|
|
|
355 |
this.grafts = Collections.emptyMap();
|
151 |
ilm |
356 |
this.postFetchLinks = Collections.emptyMap();
|
93 |
ilm |
357 |
}
|
17 |
ilm |
358 |
}
|
|
|
359 |
|
93 |
ilm |
360 |
// be aware that the new instance will share the same selTransf, and if it doesn't directly
|
|
|
361 |
// (with copyTransf) some state can still be shared
|
|
|
362 |
private SQLRowValuesListFetcher(SQLRowValuesListFetcher f, final boolean copyTransf) {
|
|
|
363 |
synchronized (f) {
|
|
|
364 |
this.graph = f.getGraph().toImmutable();
|
|
|
365 |
this.descendantPath = f.getReferentPath();
|
|
|
366 |
// can't deadlock since this hasn't been published
|
|
|
367 |
synchronized (this) {
|
|
|
368 |
this.selTransf = copyTransf ? CopyUtils.copy(f.selTransf) : f.selTransf;
|
|
|
369 |
this.selID = f.getSelID();
|
|
|
370 |
this.ordered = f.getOrder();
|
|
|
371 |
this.descendantsOrdered = f.areReferentsOrdered();
|
|
|
372 |
this.minGraph = f.minGraph == null ? null : f.minGraph.toImmutable();
|
|
|
373 |
this.includeForeignUndef = f.includeForeignUndef;
|
|
|
374 |
// a new instance is always mutable
|
|
|
375 |
this.frozen = null;
|
|
|
376 |
|
|
|
377 |
this.freezeRows = f.freezeRows;
|
|
|
378 |
|
|
|
379 |
// Recursively copy grafts
|
|
|
380 |
final Map<Path, Map<Path, SQLRowValuesListFetcher>> outerMutable = new HashMap<Path, Map<Path, SQLRowValuesListFetcher>>(f.grafts);
|
|
|
381 |
for (final Entry<Path, Map<Path, SQLRowValuesListFetcher>> e : outerMutable.entrySet()) {
|
|
|
382 |
final Map<Path, SQLRowValuesListFetcher> innerMutable = new HashMap<Path, SQLRowValuesListFetcher>(e.getValue());
|
|
|
383 |
for (final Entry<Path, SQLRowValuesListFetcher> innerEntry : innerMutable.entrySet()) {
|
|
|
384 |
innerEntry.setValue(new SQLRowValuesListFetcher(innerEntry.getValue(), copyTransf));
|
|
|
385 |
}
|
|
|
386 |
e.setValue(Collections.unmodifiableMap(innerMutable));
|
|
|
387 |
}
|
|
|
388 |
this.grafts = Collections.unmodifiableMap(outerMutable);
|
151 |
ilm |
389 |
this.postFetchLinks = f.postFetchLinks;
|
93 |
ilm |
390 |
}
|
|
|
391 |
}
|
|
|
392 |
}
|
|
|
393 |
|
17 |
ilm |
394 |
/**
|
93 |
ilm |
395 |
* Get a frozen version of this. If not already {@link #isFrozen() frozen}, copy this and its
|
|
|
396 |
* grafts and {@link #freeze()} the copy.
|
|
|
397 |
*
|
|
|
398 |
* @return <code>this</code> if already frozen, otherwise a frozen copy of <code>this</code>.
|
|
|
399 |
*/
|
|
|
400 |
public final SQLRowValuesListFetcher toUnmodifiable() {
|
|
|
401 |
synchronized (this) {
|
|
|
402 |
if (this.isFrozen())
|
|
|
403 |
return this;
|
|
|
404 |
// no need to try to deep copy since we freeze before releasing the lock
|
|
|
405 |
return new SQLRowValuesListFetcher(this, false).freeze();
|
|
|
406 |
}
|
|
|
407 |
}
|
|
|
408 |
|
|
|
409 |
/**
|
17 |
ilm |
410 |
* Make this instance immutable. Ie all setters will now throw {@link IllegalStateException}.
|
|
|
411 |
* Furthermore the request will be computed now once and for all, so as not to be subject to
|
132 |
ilm |
412 |
* outside modification by {@link #getSelectTransformers()}.
|
17 |
ilm |
413 |
*
|
|
|
414 |
* @return this.
|
|
|
415 |
*/
|
93 |
ilm |
416 |
public synchronized final SQLRowValuesListFetcher freeze() {
|
17 |
ilm |
417 |
if (!this.isFrozen()) {
|
|
|
418 |
this.frozen = new SQLSelect(this.getReq());
|
|
|
419 |
for (final Map<Path, SQLRowValuesListFetcher> m : this.grafts.values()) {
|
|
|
420 |
for (final SQLRowValuesListFetcher f : m.values())
|
|
|
421 |
f.freeze();
|
|
|
422 |
}
|
|
|
423 |
}
|
|
|
424 |
return this;
|
|
|
425 |
}
|
|
|
426 |
|
93 |
ilm |
427 |
public synchronized final boolean isFrozen() {
|
17 |
ilm |
428 |
return this.frozen != null;
|
|
|
429 |
}
|
|
|
430 |
|
|
|
431 |
private final void checkFrozen() {
|
|
|
432 |
if (this.isFrozen())
|
|
|
433 |
throw new IllegalStateException("this has been frozen: " + this);
|
|
|
434 |
}
|
|
|
435 |
|
93 |
ilm |
436 |
/**
|
|
|
437 |
* Whether the rows returned by {@link #fetch()} are {@link SQLRowValues#isFrozen()
|
|
|
438 |
* unmodifiable}.
|
|
|
439 |
*
|
|
|
440 |
* @param b <code>true</code> to make all rows unmodifiable.
|
|
|
441 |
*/
|
|
|
442 |
public synchronized final void setReturnedRowsUnmodifiable(final boolean b) {
|
|
|
443 |
this.checkFrozen();
|
|
|
444 |
this.freezeRows = b;
|
|
|
445 |
}
|
|
|
446 |
|
|
|
447 |
/**
|
|
|
448 |
* Whether the rows returned by {@link #fetch()} are {@link SQLRowValues#isFrozen()
|
|
|
449 |
* unmodifiable}.
|
|
|
450 |
*
|
|
|
451 |
* @return <code>true</code> if all rows are returned unmodifiable.
|
|
|
452 |
*/
|
|
|
453 |
public synchronized boolean areReturnedRowsUnmodifiable() {
|
|
|
454 |
return this.freezeRows;
|
|
|
455 |
}
|
|
|
456 |
|
17 |
ilm |
457 |
public SQLRowValues getGraph() {
|
|
|
458 |
return this.graph;
|
|
|
459 |
}
|
|
|
460 |
|
80 |
ilm |
461 |
public final Path getReferentPath() {
|
|
|
462 |
return this.descendantPath;
|
|
|
463 |
}
|
|
|
464 |
|
17 |
ilm |
465 |
/**
|
|
|
466 |
* Whether to include undefined rows (of tables other than the graph's).
|
|
|
467 |
*
|
|
|
468 |
* @param includeForeignUndef <code>true</code> to include undefined rows.
|
|
|
469 |
*/
|
93 |
ilm |
470 |
public synchronized final void setIncludeForeignUndef(boolean includeForeignUndef) {
|
17 |
ilm |
471 |
this.checkFrozen();
|
|
|
472 |
this.includeForeignUndef = includeForeignUndef;
|
|
|
473 |
}
|
|
|
474 |
|
|
|
475 |
/**
|
|
|
476 |
* Require that only rows with values for the full graph are returned. Eg if the graph is CPI ->
|
|
|
477 |
* OBS, setting this to <code>true</code> will excludes CPI without OBS.
|
|
|
478 |
*
|
|
|
479 |
* @param b <code>true</code> if only full rows should be fetched.
|
|
|
480 |
*/
|
93 |
ilm |
481 |
public synchronized final void setFullOnly(boolean b) {
|
17 |
ilm |
482 |
this.checkFrozen();
|
|
|
483 |
if (b)
|
61 |
ilm |
484 |
this.minGraph = this.getGraph().deepCopy();
|
17 |
ilm |
485 |
else
|
|
|
486 |
this.minGraph = null;
|
|
|
487 |
}
|
|
|
488 |
|
144 |
ilm |
489 |
// MAYBE allow to remove by changing to
|
|
|
490 |
// addRequiredPath(Path)
|
|
|
491 |
// removeRequiredPath(Path)
|
|
|
492 |
// -> just a Set of Path, reduced at the start of fetch()
|
93 |
ilm |
493 |
public synchronized final void requirePath(final Path p) {
|
61 |
ilm |
494 |
this.checkFrozen();
|
|
|
495 |
if (this.getGraph().followPath(p) == null)
|
67 |
ilm |
496 |
throw new IllegalArgumentException("Path not included in this graph : " + p + "\n" + this.getGraph().printGraph());
|
61 |
ilm |
497 |
if (this.minGraph == null)
|
|
|
498 |
this.minGraph = new SQLRowValues(getGraph().getTable());
|
|
|
499 |
this.minGraph.assurePath(p);
|
|
|
500 |
}
|
|
|
501 |
|
93 |
ilm |
502 |
private synchronized final boolean isPathRequired(final Path p) {
|
17 |
ilm |
503 |
return this.minGraph != null && this.minGraph.followPath(p) != null;
|
|
|
504 |
}
|
|
|
505 |
|
|
|
506 |
private boolean fetchReferents() {
|
61 |
ilm |
507 |
return this.descendantPath.length() > 0;
|
17 |
ilm |
508 |
}
|
|
|
509 |
|
|
|
510 |
/**
|
|
|
511 |
* To modify the query before execution.
|
|
|
512 |
*
|
|
|
513 |
* @param selTransf will be passed the query which has been constructed, and the return value
|
|
|
514 |
* will be actually executed, can be <code>null</code>.
|
|
|
515 |
*/
|
93 |
ilm |
516 |
public synchronized void setSelTransf(ITransformer<SQLSelect, SQLSelect> selTransf) {
|
17 |
ilm |
517 |
this.checkFrozen();
|
132 |
ilm |
518 |
this.selTransf = selTransf != null ? Collections.singletonList(selTransf) : Collections.<ITransformer<SQLSelect, SQLSelect>> emptyList();
|
17 |
ilm |
519 |
}
|
|
|
520 |
|
132 |
ilm |
521 |
public void clearSelTransf() {
|
|
|
522 |
this.setSelTransf(null);
|
|
|
523 |
}
|
|
|
524 |
|
|
|
525 |
public void appendSelTransf(ITransformer<SQLSelect, SQLSelect> selTransf) {
|
|
|
526 |
this.addSelTransf(selTransf, -1);
|
|
|
527 |
}
|
|
|
528 |
|
|
|
529 |
public void prependSelTransf(ITransformer<SQLSelect, SQLSelect> selTransf) {
|
|
|
530 |
this.addSelTransf(selTransf, 0);
|
|
|
531 |
}
|
|
|
532 |
|
|
|
533 |
public synchronized void addSelTransf(ITransformer<SQLSelect, SQLSelect> selTransf, final int index) {
|
|
|
534 |
this.checkFrozen();
|
|
|
535 |
if (selTransf != null) {
|
|
|
536 |
final List<ITransformer<SQLSelect, SQLSelect>> copy = new ArrayList<ITransformer<SQLSelect, SQLSelect>>(this.selTransf);
|
|
|
537 |
final int size = copy.size();
|
|
|
538 |
final int realIndex = index < 0 ? size + index + 1 : index;
|
|
|
539 |
copy.add(realIndex, selTransf);
|
|
|
540 |
this.selTransf = Collections.unmodifiableList(copy);
|
|
|
541 |
}
|
|
|
542 |
}
|
|
|
543 |
|
|
|
544 |
public synchronized boolean removeSelTransf(ITransformer<SQLSelect, SQLSelect> selTransf) {
|
|
|
545 |
this.checkFrozen();
|
|
|
546 |
if (selTransf != null) {
|
|
|
547 |
final List<ITransformer<SQLSelect, SQLSelect>> copy = new ArrayList<ITransformer<SQLSelect, SQLSelect>>(this.selTransf);
|
|
|
548 |
if (copy.remove(selTransf)) {
|
|
|
549 |
this.selTransf = Collections.unmodifiableList(copy);
|
|
|
550 |
return true;
|
|
|
551 |
}
|
|
|
552 |
}
|
|
|
553 |
return false;
|
|
|
554 |
}
|
|
|
555 |
|
93 |
ilm |
556 |
public synchronized final ITransformer<SQLSelect, SQLSelect> getSelTransf() {
|
132 |
ilm |
557 |
if (this.selTransf.size() > 1)
|
|
|
558 |
throw new IllegalStateException("More than one transformer");
|
|
|
559 |
return CollectionUtils.getFirst(this.selTransf);
|
|
|
560 |
}
|
|
|
561 |
|
|
|
562 |
public synchronized final List<ITransformer<SQLSelect, SQLSelect>> getSelectTransformers() {
|
17 |
ilm |
563 |
return this.selTransf;
|
|
|
564 |
}
|
|
|
565 |
|
|
|
566 |
/**
|
|
|
567 |
* Add a where in {@link #getReq()} to restrict the primary key.
|
|
|
568 |
*
|
|
|
569 |
* @param selID an ID for the primary key, <code>null</code> to not filter.
|
|
|
570 |
*/
|
93 |
ilm |
571 |
public synchronized void setSelID(Number selID) {
|
17 |
ilm |
572 |
this.checkFrozen();
|
|
|
573 |
this.selID = selID;
|
|
|
574 |
}
|
|
|
575 |
|
93 |
ilm |
576 |
public synchronized final Number getSelID() {
|
17 |
ilm |
577 |
return this.selID;
|
|
|
578 |
}
|
|
|
579 |
|
|
|
580 |
/**
|
|
|
581 |
* Whether to add ORDER BY in {@link #getReq()}.
|
|
|
582 |
*
|
|
|
583 |
* @param b <code>true</code> if the query should be ordered.
|
65 |
ilm |
584 |
* @return this.
|
17 |
ilm |
585 |
*/
|
93 |
ilm |
586 |
public synchronized final SQLRowValuesListFetcher setOrdered(final boolean b) {
|
80 |
ilm |
587 |
this.setOrder(b ? Collections.singleton(Path.get(getGraph().getTable())) : Collections.<Path> emptySet(), true);
|
73 |
ilm |
588 |
this.setReferentsOrdered(b, false);
|
|
|
589 |
return this;
|
|
|
590 |
}
|
|
|
591 |
|
|
|
592 |
public final SQLRowValuesListFetcher setOrder(final List<Path> order) {
|
|
|
593 |
return this.setOrder(order, false);
|
|
|
594 |
}
|
|
|
595 |
|
93 |
ilm |
596 |
private synchronized final SQLRowValuesListFetcher setOrder(final Collection<Path> order, final boolean safeVal) {
|
17 |
ilm |
597 |
this.checkFrozen();
|
73 |
ilm |
598 |
for (final Path p : order)
|
|
|
599 |
if (this.getGraph().followPath(p) == null)
|
|
|
600 |
throw new IllegalArgumentException("Path not in this " + p);
|
|
|
601 |
this.ordered = safeVal ? (Set<Path>) order : Collections.unmodifiableSet(new LinkedHashSet<Path>(order));
|
65 |
ilm |
602 |
return this;
|
17 |
ilm |
603 |
}
|
|
|
604 |
|
93 |
ilm |
605 |
public synchronized final Set<Path> getOrder() {
|
17 |
ilm |
606 |
return this.ordered;
|
|
|
607 |
}
|
|
|
608 |
|
73 |
ilm |
609 |
/**
|
|
|
610 |
* Whether to order referent rows in this fetcher.
|
|
|
611 |
*
|
|
|
612 |
* @param b <code>true</code> to order referent rows starting from the primary node, e.g. if the
|
|
|
613 |
* graph is
|
|
|
614 |
*
|
|
|
615 |
* <pre>
|
|
|
616 |
* *SITE* <- BATIMENT <- LOCAL
|
132 |
ilm |
617 |
* </pre>
|
73 |
ilm |
618 |
*
|
|
|
619 |
* then this will cause ORDER BY BATIMENT.ORDRE, LOCAL.ORDRE.
|
|
|
620 |
* @param rec if grafts should also be changed.
|
|
|
621 |
* @return this.
|
|
|
622 |
*/
|
93 |
ilm |
623 |
public synchronized final SQLRowValuesListFetcher setReferentsOrdered(final boolean b, final boolean rec) {
|
73 |
ilm |
624 |
this.descendantsOrdered = b;
|
|
|
625 |
if (rec) {
|
|
|
626 |
for (final Map<Path, SQLRowValuesListFetcher> m : this.grafts.values()) {
|
|
|
627 |
for (final SQLRowValuesListFetcher f : m.values())
|
|
|
628 |
f.setReferentsOrdered(b, rec);
|
|
|
629 |
}
|
|
|
630 |
}
|
|
|
631 |
return this;
|
|
|
632 |
}
|
|
|
633 |
|
93 |
ilm |
634 |
public synchronized final boolean areReferentsOrdered() {
|
73 |
ilm |
635 |
return this.descendantsOrdered;
|
|
|
636 |
}
|
|
|
637 |
|
151 |
ilm |
638 |
public final void addPostFetchLink(final Path toAdd, final Path existingDestination) {
|
|
|
639 |
this.addPostFetchLink(toAdd, existingDestination, false);
|
|
|
640 |
}
|
|
|
641 |
|
|
|
642 |
/**
|
|
|
643 |
* Add a link to be added at the end of fetch(). This is needed when the graph to be fetched
|
|
|
644 |
* isn't a tree.
|
|
|
645 |
*
|
|
|
646 |
* @param toAdd the last step of this parameter will be added at the end of {@link #fetch()},
|
|
|
647 |
* e.g. /SOURCE/ --[ID_MOST_SERIOUS_OBS]--> /OBSERVATION/.
|
|
|
648 |
* @param existingDestination where the destination rows of <code>toAdd</code> are, e.g.
|
|
|
649 |
* /SOURCE/ <--[ID_SOURCE]-- /SOURCE_OBSERVATION/ --[ID_OBSERVATION]--> /OBSERVATION/.
|
|
|
650 |
* @param ignoreIfMissing what to do if a passed path isn't in this, <code>true</code> to do
|
|
|
651 |
* nothing, <code>false</code> to throw an exception.
|
|
|
652 |
* @return <code>true</code> if the link was added.
|
|
|
653 |
*/
|
|
|
654 |
public synchronized final boolean addPostFetchLink(final Path toAdd, final Path existingDestination, final boolean ignoreIfMissing) {
|
|
|
655 |
checkFrozen();
|
|
|
656 |
if (toAdd.getLast() != existingDestination.getLast())
|
|
|
657 |
throw new IllegalArgumentException("Different destination tables");
|
|
|
658 |
if (!toAdd.isSingleField())
|
|
|
659 |
throw new IllegalArgumentException("Path to add isn't composed of single fields");
|
|
|
660 |
final Step lastStep = toAdd.getStep(-1);
|
|
|
661 |
if (lastStep.getDirection() != Direction.FOREIGN)
|
|
|
662 |
throw new IllegalArgumentException("Last step isn't foreign : " + lastStep);
|
|
|
663 |
if (!getFetchers(toAdd).isEmpty())
|
|
|
664 |
throw new IllegalArgumentException("Path to add already fetched");
|
|
|
665 |
final Path pathToFK = toAdd.minusLast();
|
|
|
666 |
final ListMap<Path, SQLRowValuesListFetcher> fkFetchers = getFetchers(pathToFK);
|
|
|
667 |
if (fkFetchers.isEmpty()) {
|
|
|
668 |
if (ignoreIfMissing)
|
|
|
669 |
return false;
|
|
|
670 |
else
|
|
|
671 |
throw new IllegalArgumentException("Path to add should only have the last step missing");
|
|
|
672 |
}
|
|
|
673 |
final String lastFieldName = lastStep.getSingleField().getName();
|
|
|
674 |
for (final Entry<Path, List<SQLRowValuesListFetcher>> e : fkFetchers.entrySet()) {
|
|
|
675 |
final int pathLength = e.getKey().length();
|
|
|
676 |
for (final SQLRowValuesListFetcher fkFetcher : e.getValue()) {
|
|
|
677 |
if (!fkFetcher.getGraph().followPath(pathToFK.subPath(pathLength)).contains(lastFieldName)) {
|
|
|
678 |
if (ignoreIfMissing)
|
|
|
679 |
return false;
|
|
|
680 |
else
|
|
|
681 |
throw new IllegalArgumentException("Foreign key " + lastFieldName + " isn't fetched");
|
|
|
682 |
}
|
|
|
683 |
}
|
|
|
684 |
}
|
|
|
685 |
if (getFetchers(existingDestination).isEmpty()) {
|
|
|
686 |
if (ignoreIfMissing)
|
|
|
687 |
return false;
|
|
|
688 |
else
|
|
|
689 |
throw new IllegalArgumentException("Destination won't be fetched : " + existingDestination);
|
|
|
690 |
}
|
|
|
691 |
final Map<Path, Path> copy = new HashMap<>(this.postFetchLinks);
|
|
|
692 |
copy.put(toAdd, existingDestination);
|
|
|
693 |
this.postFetchLinks = Collections.unmodifiableMap(copy);
|
|
|
694 |
return true;
|
|
|
695 |
}
|
|
|
696 |
|
|
|
697 |
public synchronized Map<Path, Path> getPostFetchLinks() {
|
|
|
698 |
return this.postFetchLinks;
|
|
|
699 |
}
|
|
|
700 |
|
17 |
ilm |
701 |
public final SQLRowValuesListFetcher graft(final SQLRowValuesListFetcher other) {
|
80 |
ilm |
702 |
return this.graft(other, Path.get(getGraph().getTable()));
|
17 |
ilm |
703 |
}
|
|
|
704 |
|
|
|
705 |
public final SQLRowValuesListFetcher graft(final SQLRowValues other, Path graftPath) {
|
|
|
706 |
// with referents otherwise it's useless
|
|
|
707 |
return this.graft(new SQLRowValuesListFetcher(other, true), graftPath);
|
|
|
708 |
}
|
|
|
709 |
|
|
|
710 |
/**
|
|
|
711 |
* Graft a fetcher on this graph.
|
|
|
712 |
*
|
|
|
713 |
* @param other another instance fetching rows of the table at <code>graftPath</code>.
|
|
|
714 |
* @param graftPath a path from this values to where <code>other</code> rows should be grafted.
|
|
|
715 |
* @return the previous fetcher.
|
|
|
716 |
*/
|
93 |
ilm |
717 |
public synchronized final SQLRowValuesListFetcher graft(final SQLRowValuesListFetcher other, Path graftPath) {
|
17 |
ilm |
718 |
checkFrozen();
|
|
|
719 |
if (this == other)
|
|
|
720 |
throw new IllegalArgumentException("trying to graft onto itself");
|
|
|
721 |
if (other.getGraph().getTable() != graftPath.getLast())
|
|
|
722 |
throw new IllegalArgumentException("trying to graft " + other.getGraph().getTable() + " at " + graftPath);
|
|
|
723 |
final SQLRowValues graftPlace = this.getGraph().followPath(graftPath);
|
|
|
724 |
if (graftPlace == null)
|
|
|
725 |
throw new IllegalArgumentException("path doesn't exist: " + graftPath);
|
|
|
726 |
assert graftPath.getLast() == graftPlace.getTable();
|
83 |
ilm |
727 |
if (other.getGraph().hasForeigns())
|
17 |
ilm |
728 |
throw new IllegalArgumentException("shouldn't have foreign rows");
|
|
|
729 |
|
144 |
ilm |
730 |
final Path descendantPath = other.getReferentPath();
|
61 |
ilm |
731 |
final int descendantPathLength = descendantPath.length();
|
|
|
732 |
if (descendantPathLength == 0)
|
17 |
ilm |
733 |
throw new IllegalArgumentException("empty path");
|
|
|
734 |
// checked by computePath
|
93 |
ilm |
735 |
assert descendantPath.isSingleField();
|
65 |
ilm |
736 |
// we used to disallow that :
|
|
|
737 |
// this is LOCAL* -> BATIMENT -> SITE and CPI -> LOCAL -> BATIMENT* is being grafted
|
|
|
738 |
// but this is sometimes desirable, e.g. for each LOCAL find all of its siblings with the
|
|
|
739 |
// same capacity (or any other predicate)
|
|
|
740 |
|
93 |
ilm |
741 |
// shallow copy : all values are still immutable
|
|
|
742 |
final Map<Path, Map<Path, SQLRowValuesListFetcher>> outerMutable = new HashMap<Path, Map<Path, SQLRowValuesListFetcher>>(this.grafts);
|
|
|
743 |
final Map<Path, SQLRowValuesListFetcher> innerMutable;
|
17 |
ilm |
744 |
if (!this.grafts.containsKey(graftPath)) {
|
80 |
ilm |
745 |
// allow getFetchers() to use a list, easing tests and avoiding using equals()
|
93 |
ilm |
746 |
innerMutable = new LinkedHashMap<Path, SQLRowValuesListFetcher>(4);
|
61 |
ilm |
747 |
} else {
|
|
|
748 |
final Map<Path, SQLRowValuesListFetcher> map = this.grafts.get(graftPath);
|
93 |
ilm |
749 |
innerMutable = new LinkedHashMap<Path, SQLRowValuesListFetcher>(map);
|
61 |
ilm |
750 |
// e.g. fetching *BATIMENT* <- LOCAL and *BATIMENT* <- LOCAL <- CPI (with different
|
|
|
751 |
// WHERE) and LOCAL have different fields. This isn't supported since we would have to
|
|
|
752 |
// merge fields in merge() and it would be quite long
|
|
|
753 |
for (Entry<Path, SQLRowValuesListFetcher> e : map.entrySet()) {
|
|
|
754 |
final Path fetcherPath = e.getKey();
|
|
|
755 |
final SQLRowValuesListFetcher fetcher = e.getValue();
|
|
|
756 |
for (int i = 1; i <= descendantPathLength; i++) {
|
|
|
757 |
final Path subPath = descendantPath.subPath(0, i);
|
|
|
758 |
if (fetcherPath.startsWith(subPath)) {
|
|
|
759 |
if (!fetcher.getGraph().followPath(subPath).getFields().equals(other.getGraph().followPath(subPath).getFields()))
|
|
|
760 |
throw new IllegalArgumentException("The same node have different fields in different fetcher\n" + graftPath + "\n" + subPath);
|
|
|
761 |
} else {
|
|
|
762 |
break;
|
|
|
763 |
}
|
|
|
764 |
}
|
17 |
ilm |
765 |
}
|
|
|
766 |
}
|
93 |
ilm |
767 |
final SQLRowValuesListFetcher res = innerMutable.put(descendantPath, other);
|
|
|
768 |
outerMutable.put(graftPath, Collections.unmodifiableMap(innerMutable));
|
|
|
769 |
this.grafts = Collections.unmodifiableMap(outerMutable);
|
|
|
770 |
return res;
|
17 |
ilm |
771 |
}
|
|
|
772 |
|
61 |
ilm |
773 |
public final Collection<SQLRowValuesListFetcher> ungraft() {
|
80 |
ilm |
774 |
return this.ungraft(Path.get(getGraph().getTable()));
|
17 |
ilm |
775 |
}
|
|
|
776 |
|
93 |
ilm |
777 |
public synchronized final Collection<SQLRowValuesListFetcher> ungraft(final Path graftPath) {
|
17 |
ilm |
778 |
checkFrozen();
|
93 |
ilm |
779 |
if (!this.grafts.containsKey(graftPath))
|
|
|
780 |
return null;
|
|
|
781 |
final Map<Path, Map<Path, SQLRowValuesListFetcher>> outerMutable = new HashMap<Path, Map<Path, SQLRowValuesListFetcher>>(this.grafts);
|
|
|
782 |
final Map<Path, SQLRowValuesListFetcher> res = outerMutable.remove(graftPath);
|
|
|
783 |
this.grafts = Collections.unmodifiableMap(outerMutable);
|
61 |
ilm |
784 |
return res == null ? null : res.values();
|
17 |
ilm |
785 |
}
|
|
|
786 |
|
93 |
ilm |
787 |
private synchronized final Map<Path, Map<Path, SQLRowValuesListFetcher>> getGrafts() {
|
|
|
788 |
return this.grafts;
|
|
|
789 |
}
|
|
|
790 |
|
67 |
ilm |
791 |
/**
|
|
|
792 |
* The fetchers grafted at the passed path.
|
|
|
793 |
*
|
|
|
794 |
* @param graftPath where the fetchers are grafted, e.g. MISSION, DOSSIER, SITE.
|
|
|
795 |
* @return the grafts by their path to fetch, e.g. SITE, BATIMENT, LOCAL, CPI_BT.
|
|
|
796 |
*/
|
|
|
797 |
public final Map<Path, SQLRowValuesListFetcher> getGrafts(final Path graftPath) {
|
93 |
ilm |
798 |
return this.getGrafts().get(graftPath);
|
67 |
ilm |
799 |
}
|
|
|
800 |
|
80 |
ilm |
801 |
/**
|
83 |
ilm |
802 |
* Get all fetchers.
|
|
|
803 |
*
|
|
|
804 |
* @param includeSelf <code>true</code> to include <code>this</code> (with a <code>null</code>
|
|
|
805 |
* key).
|
|
|
806 |
* @return all instances indexed by the graft path.
|
|
|
807 |
*/
|
|
|
808 |
public final ListMapItf<Path, SQLRowValuesListFetcher> getFetchers(final boolean includeSelf) {
|
|
|
809 |
final ListMap<Path, SQLRowValuesListFetcher> res = new ListMap<Path, SQLRowValuesListFetcher>();
|
93 |
ilm |
810 |
for (final Entry<Path, Map<Path, SQLRowValuesListFetcher>> e : this.getGrafts().entrySet()) {
|
83 |
ilm |
811 |
assert e.getKey() != null;
|
|
|
812 |
res.putCollection(e.getKey(), e.getValue().values());
|
|
|
813 |
}
|
|
|
814 |
if (includeSelf)
|
|
|
815 |
res.add(null, this);
|
|
|
816 |
return ListMap.unmodifiableMap(res);
|
|
|
817 |
}
|
|
|
818 |
|
|
|
819 |
/**
|
80 |
ilm |
820 |
* Get instances which fetch the {@link Path#getLast() last table} of the passed path. E.g.
|
|
|
821 |
* useful if you want to add a where to a join. This method is recursively called on
|
|
|
822 |
* {@link #getGrafts(Path) grafts} thus the returned paths may be fetched by grafts.
|
|
|
823 |
*
|
|
|
824 |
* @param fetchedPath a path starting by this table.
|
|
|
825 |
* @return all instances indexed by the graft path, i.e. <code>fetchedPath</code> is between
|
|
|
826 |
* with it and (it+fetchers.{@link #getReferentPath()}).
|
|
|
827 |
*/
|
|
|
828 |
public final ListMap<Path, SQLRowValuesListFetcher> getFetchers(final Path fetchedPath) {
|
|
|
829 |
final ListMap<Path, SQLRowValuesListFetcher> res = new ListMap<Path, SQLRowValuesListFetcher>();
|
|
|
830 |
if (this.getGraph().followPath(fetchedPath) != null)
|
|
|
831 |
res.add(Path.get(getGraph().getTable()), this);
|
|
|
832 |
// search grafts
|
93 |
ilm |
833 |
for (final Entry<Path, Map<Path, SQLRowValuesListFetcher>> e : this.getGrafts().entrySet()) {
|
80 |
ilm |
834 |
final Path graftPlace = e.getKey();
|
|
|
835 |
if (fetchedPath.startsWith(graftPlace) && fetchedPath.length() > graftPlace.length()) {
|
|
|
836 |
final Path rest = fetchedPath.subPath(graftPlace.length());
|
|
|
837 |
// we want requests that use the last step of fetchedPath
|
|
|
838 |
assert rest.length() > 0;
|
|
|
839 |
for (final Entry<Path, SQLRowValuesListFetcher> e2 : e.getValue().entrySet()) {
|
|
|
840 |
final Path refPath = e2.getKey();
|
|
|
841 |
final SQLRowValuesListFetcher graft = e2.getValue();
|
|
|
842 |
if (refPath.startsWith(rest)) {
|
|
|
843 |
res.add(graftPlace, graft);
|
|
|
844 |
} else if (rest.startsWith(refPath)) {
|
|
|
845 |
// otherwise rest == refPath and the above if would have been executed
|
|
|
846 |
assert rest.length() > refPath.length();
|
|
|
847 |
for (final Entry<Path, List<SQLRowValuesListFetcher>> e3 : graft.getFetchers(rest).entrySet()) {
|
|
|
848 |
res.addAll(graftPlace.append(e3.getKey()), e3.getValue());
|
|
|
849 |
}
|
|
|
850 |
}
|
|
|
851 |
}
|
|
|
852 |
}
|
|
|
853 |
}
|
|
|
854 |
return res;
|
|
|
855 |
}
|
|
|
856 |
|
17 |
ilm |
857 |
private final void addFields(final SQLSelect sel, final SQLRowValues vals, final String alias) {
|
83 |
ilm |
858 |
// put key first
|
|
|
859 |
final SQLField key = vals.getTable().getKey();
|
|
|
860 |
sel.addSelect(new AliasedField(key, alias));
|
|
|
861 |
for (final String fieldName : vals.getFields()) {
|
|
|
862 |
if (!fieldName.equals(key.getName()))
|
|
|
863 |
sel.addSelect(new AliasedField(vals.getTable().getField(fieldName), alias));
|
|
|
864 |
}
|
17 |
ilm |
865 |
}
|
|
|
866 |
|
|
|
867 |
public final SQLSelect getReq() {
|
142 |
ilm |
868 |
return this.getReq(null, null);
|
93 |
ilm |
869 |
}
|
17 |
ilm |
870 |
|
142 |
ilm |
871 |
static private final SQLSelect checkTr(final List<String> origSelect, final SQLSelect tr) {
|
|
|
872 |
if (!origSelect.equals(tr.getSelect()))
|
|
|
873 |
throw new IllegalArgumentException("Select clause cannot be modified");
|
|
|
874 |
return tr;
|
|
|
875 |
}
|
|
|
876 |
|
|
|
877 |
public synchronized final SQLSelect getReq(final Where w, final ITransformer<SQLSelect, SQLSelect> selTransf) {
|
|
|
878 |
checkTable(w);
|
|
|
879 |
final boolean isNopTransf = selTransf == null || selTransf == Transformer.<SQLSelect> nopTransformer();
|
93 |
ilm |
880 |
if (this.isFrozen()) {
|
142 |
ilm |
881 |
if (w == null && isNopTransf) {
|
93 |
ilm |
882 |
return this.frozen;
|
|
|
883 |
} else {
|
142 |
ilm |
884 |
final SQLSelect copy = new SQLSelect(this.frozen);
|
|
|
885 |
final SQLSelect res = isNopTransf ? copy : checkTr(copy.getSelect(), selTransf.transformChecked(copy));
|
|
|
886 |
return res.andWhere(w);
|
93 |
ilm |
887 |
}
|
|
|
888 |
}
|
|
|
889 |
|
17 |
ilm |
890 |
final SQLTable t = this.getGraph().getTable();
|
73 |
ilm |
891 |
final SQLSelect sel = new SQLSelect();
|
17 |
ilm |
892 |
|
|
|
893 |
if (this.includeForeignUndef) {
|
|
|
894 |
sel.setExcludeUndefined(false);
|
|
|
895 |
sel.setExcludeUndefined(true, t);
|
|
|
896 |
}
|
|
|
897 |
|
93 |
ilm |
898 |
walk(null, new ITransformer<State<String>, String>() {
|
17 |
ilm |
899 |
@Override
|
93 |
ilm |
900 |
public String transformChecked(State<String> input) {
|
17 |
ilm |
901 |
final String alias;
|
|
|
902 |
if (input.getFrom() != null) {
|
93 |
ilm |
903 |
alias = getAlias(sel, input.getPath());
|
|
|
904 |
final String aliasPrev = input.getAcc();
|
144 |
ilm |
905 |
// MAYBE use "INNER" for first step of a referent graft since the first node is
|
|
|
906 |
// ignored (the node from the parent graph is used)
|
|
|
907 |
// SITE <-- BATIMENT and graft is BATIMENT <-- LOCAL, empty BATIMENT are just
|
|
|
908 |
// discarded so best not to fetch them for nothing
|
61 |
ilm |
909 |
final String joinType = isPathRequired(input.getPath()) ? "INNER" : "LEFT";
|
93 |
ilm |
910 |
sel.addJoin(joinType, aliasPrev, input.getPath().getStep(-1), alias);
|
83 |
ilm |
911 |
} else {
|
17 |
ilm |
912 |
alias = null;
|
83 |
ilm |
913 |
}
|
93 |
ilm |
914 |
addFields(sel, input.getCurrent(), alias);
|
17 |
ilm |
915 |
|
93 |
ilm |
916 |
return alias;
|
17 |
ilm |
917 |
}
|
|
|
918 |
|
|
|
919 |
});
|
73 |
ilm |
920 |
for (final Path p : this.getOrder())
|
|
|
921 |
sel.addOrder(sel.followPath(t.getName(), p), false);
|
|
|
922 |
// after getOrder() since it can specify more precise order
|
|
|
923 |
if (this.areReferentsOrdered()) {
|
|
|
924 |
final int descSize = this.descendantPath.length();
|
|
|
925 |
for (int i = 1; i <= descSize; i++) {
|
|
|
926 |
sel.addOrder(sel.followPath(t.getName(), this.descendantPath.subPath(0, i)), false);
|
|
|
927 |
}
|
|
|
928 |
}
|
|
|
929 |
|
93 |
ilm |
930 |
if (this.getSelID() != null)
|
144 |
ilm |
931 |
sel.andWhere(getIDWhere(this.getSelID()));
|
142 |
ilm |
932 |
final List<String> origSel = new ArrayList<String>(sel.getSelect());
|
132 |
ilm |
933 |
SQLSelect res = sel;
|
|
|
934 |
for (final ITransformer<SQLSelect, SQLSelect> tr : this.getSelectTransformers()) {
|
|
|
935 |
res = tr.transformChecked(res);
|
|
|
936 |
}
|
142 |
ilm |
937 |
if (!isNopTransf)
|
|
|
938 |
res = selTransf.transformChecked(res);
|
|
|
939 |
return checkTr(origSel, res).andWhere(w);
|
17 |
ilm |
940 |
}
|
|
|
941 |
|
144 |
ilm |
942 |
public final Where getIDWhere(final Number id) {
|
132 |
ilm |
943 |
if (id == null)
|
|
|
944 |
return null;
|
144 |
ilm |
945 |
return new Where(getGraph().getTable().getKey(), "=", id);
|
132 |
ilm |
946 |
}
|
|
|
947 |
|
17 |
ilm |
948 |
static String getAlias(final SQLSelect sel, final Path path) {
|
|
|
949 |
String res = "tAlias";
|
|
|
950 |
final int stop = path.length();
|
|
|
951 |
for (int i = 0; i < stop; i++) {
|
93 |
ilm |
952 |
res += "__" + path.getSingleField(i).getName();
|
17 |
ilm |
953 |
}
|
|
|
954 |
// needed for backward, otherwise tableAlias__ID_BATIMENT for LOCAL
|
|
|
955 |
res += "__" + path.getTable(stop).getName();
|
|
|
956 |
return sel.getUniqueAlias(res);
|
|
|
957 |
}
|
|
|
958 |
|
|
|
959 |
// assure that the graph is explored the same way for the construction of the request
|
|
|
960 |
// and the reading of the resultSet
|
|
|
961 |
private <S> void walk(final S sel, final ITransformer<State<S>, S> transf) {
|
|
|
962 |
// walk through foreign keys and never walk back (use graft())
|
80 |
ilm |
963 |
this.getGraph().getGraph().walk(this.getGraph(), sel, transf, RecursionType.BREADTH_FIRST, Direction.FOREIGN);
|
17 |
ilm |
964 |
// walk starting backwards but allowing forwards
|
|
|
965 |
this.getGraph().getGraph().walk(this.getGraph(), sel, new ITransformer<State<S>, S>() {
|
|
|
966 |
@Override
|
|
|
967 |
public S transformChecked(State<S> input) {
|
|
|
968 |
final Path p = input.getPath();
|
|
|
969 |
if (p.getStep(0).isForeign())
|
|
|
970 |
throw new SQLRowValuesCluster.StopRecurseException().setCompletely(false);
|
|
|
971 |
final Step lastStep = p.getStep(p.length() - 1);
|
|
|
972 |
// if we go backwards it should be from the start (i.e. we can't go backwards, then
|
|
|
973 |
// forwards and backwards again)
|
67 |
ilm |
974 |
if (!lastStep.isForeign() && p.getDirection() != Direction.REFERENT)
|
17 |
ilm |
975 |
throw new SQLRowValuesCluster.StopRecurseException().setCompletely(false);
|
|
|
976 |
return transf.transformChecked(input);
|
|
|
977 |
}
|
83 |
ilm |
978 |
}, new WalkOptions(Direction.ANY).setRecursionType(RecursionType.BREADTH_FIRST).setStartIncluded(false));
|
17 |
ilm |
979 |
}
|
|
|
980 |
|
|
|
981 |
// models the graph, so that we don't have to walk it for each row
|
|
|
982 |
private static final class GraphNode {
|
|
|
983 |
private final SQLTable t;
|
|
|
984 |
private final int fieldCount;
|
83 |
ilm |
985 |
private final int foreignCount;
|
17 |
ilm |
986 |
private final int linkIndex;
|
|
|
987 |
private final Step from;
|
|
|
988 |
|
|
|
989 |
private GraphNode(final State<Integer> input) {
|
|
|
990 |
super();
|
|
|
991 |
this.t = input.getCurrent().getTable();
|
|
|
992 |
this.fieldCount = input.getCurrent().size();
|
83 |
ilm |
993 |
this.foreignCount = input.getCurrent().getForeigns().size();
|
17 |
ilm |
994 |
this.linkIndex = input.getAcc();
|
|
|
995 |
final int length = input.getPath().length();
|
|
|
996 |
this.from = length == 0 ? null : input.getPath().getStep(length - 1);
|
|
|
997 |
}
|
|
|
998 |
|
|
|
999 |
public final SQLTable getTable() {
|
|
|
1000 |
return this.t;
|
|
|
1001 |
}
|
|
|
1002 |
|
|
|
1003 |
public final int getFieldCount() {
|
|
|
1004 |
return this.fieldCount;
|
|
|
1005 |
}
|
|
|
1006 |
|
83 |
ilm |
1007 |
public final int getForeignCount() {
|
|
|
1008 |
return this.foreignCount;
|
|
|
1009 |
}
|
|
|
1010 |
|
17 |
ilm |
1011 |
public final int getLinkIndex() {
|
|
|
1012 |
return this.linkIndex;
|
|
|
1013 |
}
|
|
|
1014 |
|
|
|
1015 |
public final String getFromName() {
|
|
|
1016 |
return this.from.getSingleField().getName();
|
|
|
1017 |
}
|
|
|
1018 |
|
|
|
1019 |
public final boolean isBackwards() {
|
73 |
ilm |
1020 |
return !this.from.isForeign();
|
17 |
ilm |
1021 |
}
|
|
|
1022 |
|
|
|
1023 |
@Override
|
25 |
ilm |
1024 |
public int hashCode() {
|
|
|
1025 |
final int prime = 31;
|
|
|
1026 |
int result = 1;
|
|
|
1027 |
result = prime * result + this.fieldCount;
|
|
|
1028 |
result = prime * result + ((this.from == null) ? 0 : this.from.hashCode());
|
|
|
1029 |
result = prime * result + this.linkIndex;
|
|
|
1030 |
result = prime * result + this.t.hashCode();
|
|
|
1031 |
return result;
|
|
|
1032 |
}
|
|
|
1033 |
|
|
|
1034 |
@Override
|
|
|
1035 |
public boolean equals(Object obj) {
|
|
|
1036 |
if (this == obj)
|
|
|
1037 |
return true;
|
|
|
1038 |
if (obj == null)
|
|
|
1039 |
return false;
|
|
|
1040 |
if (getClass() != obj.getClass())
|
|
|
1041 |
return false;
|
|
|
1042 |
final GraphNode other = (GraphNode) obj;
|
|
|
1043 |
return this.fieldCount == other.fieldCount && this.linkIndex == other.linkIndex && this.t.equals(other.t) && CompareUtils.equals(this.from, other.from);
|
|
|
1044 |
}
|
|
|
1045 |
|
|
|
1046 |
@Override
|
17 |
ilm |
1047 |
public String toString() {
|
|
|
1048 |
final String link = this.from == null ? "" : " linked to " + getLinkIndex() + " by " + this.getFromName() + (this.isBackwards() ? " backwards" : " forewards");
|
|
|
1049 |
return this.getFieldCount() + " fields of " + this.getTable() + link;
|
|
|
1050 |
}
|
|
|
1051 |
}
|
|
|
1052 |
|
25 |
ilm |
1053 |
static private final class RSH implements ResultSetHandler {
|
|
|
1054 |
private final List<String> selectFields;
|
|
|
1055 |
private final List<GraphNode> graphNodes;
|
93 |
ilm |
1056 |
private final boolean freezeRows;
|
25 |
ilm |
1057 |
|
93 |
ilm |
1058 |
private RSH(List<String> selectFields, List<GraphNode> l, boolean freezeRows) {
|
25 |
ilm |
1059 |
this.selectFields = selectFields;
|
|
|
1060 |
this.graphNodes = l;
|
93 |
ilm |
1061 |
this.freezeRows = freezeRows;
|
25 |
ilm |
1062 |
}
|
|
|
1063 |
|
|
|
1064 |
@Override
|
|
|
1065 |
public Object handle(final ResultSet rs) throws SQLException {
|
|
|
1066 |
final List<GraphNode> l = this.graphNodes;
|
|
|
1067 |
final int graphSize = l.size();
|
|
|
1068 |
int nextToLink = 0;
|
|
|
1069 |
final List<Future<?>> futures = new ArrayList<Future<?>>();
|
|
|
1070 |
|
|
|
1071 |
final List<SQLRowValues> res = new ArrayList<SQLRowValues>(64);
|
|
|
1072 |
final List<List<SQLRowValues>> rows = Collections.synchronizedList(new ArrayList<List<SQLRowValues>>(64));
|
|
|
1073 |
// for each rs row, create all SQLRowValues without linking them together
|
|
|
1074 |
// if we're multi-threaded, link them in another thread
|
|
|
1075 |
while (rs.next()) {
|
|
|
1076 |
int rsIndex = 1;
|
|
|
1077 |
|
|
|
1078 |
// MAYBE cancel() futures
|
|
|
1079 |
if (Thread.currentThread().isInterrupted())
|
|
|
1080 |
throw new RTInterruptedException("interrupted while fetching");
|
|
|
1081 |
final List<SQLRowValues> row = new ArrayList<SQLRowValues>(graphSize);
|
|
|
1082 |
for (int i = 0; i < graphSize; i++) {
|
|
|
1083 |
final GraphNode node = l.get(i);
|
83 |
ilm |
1084 |
final int stop = rsIndex + node.getFieldCount();
|
|
|
1085 |
final SQLRowValues creatingVals;
|
|
|
1086 |
// the PK is always first and it can only be null if there was no row, i.e. all
|
|
|
1087 |
// other fields will be null.
|
|
|
1088 |
final Object first = rs.getObject(rsIndex);
|
|
|
1089 |
if (first == null) {
|
|
|
1090 |
creatingVals = null;
|
|
|
1091 |
// don't bother reading all nulls
|
|
|
1092 |
rsIndex = stop;
|
|
|
1093 |
} else {
|
|
|
1094 |
// don't pass referent count as it can be fetched by a graft, or else
|
|
|
1095 |
// several rows might later be merged (e.g. *BATIMENT* <- LOCAL has only one
|
|
|
1096 |
// referent but all locals of a batiment will point to the same row)
|
|
|
1097 |
creatingVals = new SQLRowValues(node.getTable(), node.getFieldCount(), node.getForeignCount(), -1);
|
|
|
1098 |
put(creatingVals, rsIndex, first);
|
|
|
1099 |
rsIndex++;
|
|
|
1100 |
}
|
|
|
1101 |
if (i == 0) {
|
|
|
1102 |
if (creatingVals == null)
|
|
|
1103 |
throw new IllegalStateException("Null primary row");
|
25 |
ilm |
1104 |
res.add(creatingVals);
|
83 |
ilm |
1105 |
}
|
25 |
ilm |
1106 |
|
|
|
1107 |
for (; rsIndex < stop; rsIndex++) {
|
|
|
1108 |
try {
|
83 |
ilm |
1109 |
put(creatingVals, rsIndex, rs.getObject(rsIndex));
|
25 |
ilm |
1110 |
} catch (SQLException e) {
|
|
|
1111 |
throw new IllegalStateException("unable to fill " + creatingVals, e);
|
|
|
1112 |
}
|
|
|
1113 |
}
|
|
|
1114 |
row.add(creatingVals);
|
|
|
1115 |
}
|
|
|
1116 |
rows.add(row);
|
|
|
1117 |
// become multi-threaded only for large values
|
|
|
1118 |
final int currentCount = rows.size();
|
|
|
1119 |
if (currentCount % 1000 == 0) {
|
93 |
ilm |
1120 |
futures.add(exec.submit(new Linker(l, rows, nextToLink, currentCount, this.freezeRows)));
|
25 |
ilm |
1121 |
nextToLink = currentCount;
|
|
|
1122 |
}
|
|
|
1123 |
}
|
|
|
1124 |
final int rowSize = rows.size();
|
|
|
1125 |
assert nextToLink > 0 == futures.size() > 0;
|
|
|
1126 |
if (nextToLink > 0)
|
93 |
ilm |
1127 |
futures.add(exec.submit(new Linker(l, rows, nextToLink, rowSize, this.freezeRows)));
|
25 |
ilm |
1128 |
|
|
|
1129 |
// either link all rows, or...
|
|
|
1130 |
if (nextToLink == 0)
|
93 |
ilm |
1131 |
link(l, rows, 0, rowSize, this.freezeRows);
|
25 |
ilm |
1132 |
else {
|
|
|
1133 |
// ...wait for every one and most importantly check for any exceptions
|
|
|
1134 |
try {
|
|
|
1135 |
for (final Future<?> f : futures)
|
|
|
1136 |
f.get();
|
|
|
1137 |
} catch (Exception e) {
|
|
|
1138 |
throw new IllegalStateException("couldn't link", e);
|
|
|
1139 |
}
|
|
|
1140 |
}
|
|
|
1141 |
|
|
|
1142 |
return res;
|
|
|
1143 |
}
|
|
|
1144 |
|
83 |
ilm |
1145 |
protected void put(final SQLRowValues creatingVals, int rsIndex, final Object obj) {
|
|
|
1146 |
// -1 since rs starts at 1
|
|
|
1147 |
// field names checked only once when nodes are created
|
|
|
1148 |
creatingVals.put(this.selectFields.get(rsIndex - 1), obj, false);
|
|
|
1149 |
}
|
|
|
1150 |
|
25 |
ilm |
1151 |
@Override
|
|
|
1152 |
public int hashCode() {
|
|
|
1153 |
final int prime = 31;
|
|
|
1154 |
int result = 1;
|
|
|
1155 |
result = prime * result + this.graphNodes.hashCode();
|
|
|
1156 |
result = prime * result + this.selectFields.hashCode();
|
|
|
1157 |
return result;
|
|
|
1158 |
}
|
|
|
1159 |
|
|
|
1160 |
@Override
|
|
|
1161 |
public boolean equals(Object obj) {
|
|
|
1162 |
if (this == obj)
|
|
|
1163 |
return true;
|
|
|
1164 |
if (obj == null)
|
|
|
1165 |
return false;
|
|
|
1166 |
if (getClass() != obj.getClass())
|
|
|
1167 |
return false;
|
|
|
1168 |
final RSH other = (RSH) obj;
|
|
|
1169 |
return this.graphNodes.equals(other.graphNodes) && this.selectFields.equals(other.selectFields);
|
|
|
1170 |
}
|
|
|
1171 |
|
|
|
1172 |
}
|
|
|
1173 |
|
17 |
ilm |
1174 |
/**
|
|
|
1175 |
* Execute the request transformed by <code>selTransf</code> and return the result as a list of
|
25 |
ilm |
1176 |
* SQLRowValues. NOTE: this method doesn't use the cache of SQLDataSource.
|
17 |
ilm |
1177 |
*
|
|
|
1178 |
* @return a list of SQLRowValues, one item per row, each item having the same structure as the
|
|
|
1179 |
* SQLRowValues passed to the constructor.
|
|
|
1180 |
*/
|
|
|
1181 |
public final List<SQLRowValues> fetch() {
|
93 |
ilm |
1182 |
return this.fetch(null);
|
17 |
ilm |
1183 |
}
|
|
|
1184 |
|
93 |
ilm |
1185 |
private void checkTable(final Where w) throws IllegalArgumentException {
|
|
|
1186 |
if (w == null)
|
|
|
1187 |
return;
|
|
|
1188 |
final SQLTable t = this.getGraph().getTable();
|
|
|
1189 |
for (final FieldRef f : w.getFields()) {
|
|
|
1190 |
if (!f.getTableRef().equals(t))
|
|
|
1191 |
throw new IllegalArgumentException("Not all from the primary table " + t + " : " + w);
|
|
|
1192 |
}
|
|
|
1193 |
}
|
|
|
1194 |
|
132 |
ilm |
1195 |
public final SQLRowValues fetchOne(final Number id) {
|
|
|
1196 |
return this.fetchOne(id, null);
|
|
|
1197 |
}
|
|
|
1198 |
|
|
|
1199 |
public final SQLRowValues fetchOne(final Number id, final Boolean unmodifiableRows) {
|
|
|
1200 |
if (id == null)
|
|
|
1201 |
throw new NullPointerException("Null ID");
|
|
|
1202 |
if (this.getSelID() != null)
|
|
|
1203 |
throw new IllegalStateException("ID already set to " + getSelID());
|
144 |
ilm |
1204 |
final List<SQLRowValues> res = this.fetch(getIDWhere(id), unmodifiableRows);
|
132 |
ilm |
1205 |
if (res.size() > 1)
|
|
|
1206 |
throw new IllegalStateException("More than one row for ID " + id + " : " + res);
|
|
|
1207 |
return CollectionUtils.getFirst(res);
|
|
|
1208 |
}
|
|
|
1209 |
|
93 |
ilm |
1210 |
/**
|
|
|
1211 |
* Execute the request transformed by <code>selTransf</code> and with the passed where (even if
|
|
|
1212 |
* {@link #isFrozen()}) and return the result as a list of SQLRowValues. NOTE: this method
|
|
|
1213 |
* doesn't use the cache of SQLDataSource.
|
|
|
1214 |
*
|
|
|
1215 |
* @param w a where to {@link SQLSelect#andWhere(Where) restrict} the result, can only uses the
|
|
|
1216 |
* primary table (others have unspecified aliases), can be <code>null</code>.
|
|
|
1217 |
* @return a list of SQLRowValues, one item per row, each item having the same structure as the
|
|
|
1218 |
* SQLRowValues passed to the constructor.
|
|
|
1219 |
* @throws IllegalArgumentException if the passed where doesn't refer exclusively to the primary
|
|
|
1220 |
* table.
|
|
|
1221 |
*/
|
|
|
1222 |
public final List<SQLRowValues> fetch(final Where w) throws IllegalArgumentException {
|
|
|
1223 |
return this.fetch(w, null);
|
|
|
1224 |
}
|
|
|
1225 |
|
|
|
1226 |
/**
|
|
|
1227 |
* Execute the request transformed by <code>selTransf</code> and with the passed where (even if
|
|
|
1228 |
* {@link #isFrozen()}) and return the result as a list of SQLRowValues. NOTE: this method
|
|
|
1229 |
* doesn't use the cache of SQLDataSource.
|
|
|
1230 |
*
|
|
|
1231 |
* @param w a where to {@link SQLSelect#andWhere(Where) restrict} the result, can only uses the
|
|
|
1232 |
* primary table (others have unspecified aliases), can be <code>null</code>.
|
|
|
1233 |
* @param unmodifiableRows whether to return unmodifiable rows, <code>null</code> to use
|
|
|
1234 |
* {@link #areReturnedRowsUnmodifiable() the default}.
|
|
|
1235 |
* @return a list of SQLRowValues, one item per row, each item having the same structure as the
|
|
|
1236 |
* SQLRowValues passed to the constructor.
|
|
|
1237 |
* @throws IllegalArgumentException if the passed where doesn't refer exclusively to the primary
|
|
|
1238 |
* table.
|
|
|
1239 |
*/
|
|
|
1240 |
public final List<SQLRowValues> fetch(final Where w, final Boolean unmodifiableRows) throws IllegalArgumentException {
|
142 |
ilm |
1241 |
return this.fetch(w, null, unmodifiableRows);
|
93 |
ilm |
1242 |
}
|
|
|
1243 |
|
142 |
ilm |
1244 |
public final List<SQLRowValues> fetch(final Where w, final ITransformer<SQLSelect, SQLSelect> selTransf, final Boolean unmodifiableRows) throws IllegalArgumentException {
|
144 |
ilm |
1245 |
return this.fetch(null, w, selTransf, unmodifiableRows);
|
142 |
ilm |
1246 |
}
|
|
|
1247 |
|
144 |
ilm |
1248 |
// same object passed to all recursive calls
|
|
|
1249 |
static private final class MainResult {
|
|
|
1250 |
private final Deque<GraftState> graftStates = new LinkedList<>();
|
|
|
1251 |
|
|
|
1252 |
private MainResult() {
|
|
|
1253 |
super();
|
|
|
1254 |
}
|
|
|
1255 |
|
|
|
1256 |
private GraftState getLastGraftState() {
|
|
|
1257 |
return this.graftStates.peekLast();
|
|
|
1258 |
}
|
|
|
1259 |
|
|
|
1260 |
private void push(final List<SQLRowValues> merged, final Path graftPlace) {
|
|
|
1261 |
final GraftState graftState = this.getLastGraftState();
|
|
|
1262 |
final Path recPath = graftState == null ? null : graftState.pathFromMain;
|
|
|
1263 |
final GraftState recGraftState = new GraftState(merged, recPath, graftPlace);
|
|
|
1264 |
this.graftStates.addLast(recGraftState);
|
|
|
1265 |
}
|
|
|
1266 |
|
|
|
1267 |
private void pop() {
|
|
|
1268 |
this.graftStates.removeLast();
|
|
|
1269 |
}
|
|
|
1270 |
}
|
|
|
1271 |
|
|
|
1272 |
static private final class GraftState {
|
|
|
1273 |
|
|
|
1274 |
private final Path pathFromMain;
|
|
|
1275 |
|
|
|
1276 |
// list of BATIMENT to only fetch what's necessary
|
|
|
1277 |
private final Set<Number> ids = new HashSet<Number>();
|
|
|
1278 |
// CollectionMap since the same row can be in multiple index of merged, e.g. when
|
|
|
1279 |
// fetching *BATIMENT* -> SITE each site will be repeated as many times as it has
|
|
|
1280 |
// children and if we want their DOSSIER they must be grafted on each line.
|
|
|
1281 |
private final ListMap<Tuple2<Path, Number>, SQLRowValues> byRows = createCollectionMap();
|
|
|
1282 |
|
|
|
1283 |
private GraftState(final List<SQLRowValues> parentRows, final Path pathFromMain, final Path graftPlace) {
|
|
|
1284 |
this.pathFromMain = pathFromMain == null ? graftPlace : pathFromMain.append(graftPlace);
|
|
|
1285 |
final Path mapPath = Path.get(graftPlace.getLast());
|
|
|
1286 |
for (final SQLRowValues vals : parentRows) {
|
|
|
1287 |
// can be empty when grafting on optional row
|
|
|
1288 |
for (final SQLRowValues graftPlaceVals : vals.followPath(graftPlace, CreateMode.CREATE_NONE, false)) {
|
|
|
1289 |
this.ids.add(graftPlaceVals.getIDNumber());
|
|
|
1290 |
this.byRows.add(Tuple2.create(mapPath, graftPlaceVals.getIDNumber()), graftPlaceVals);
|
|
|
1291 |
}
|
|
|
1292 |
}
|
|
|
1293 |
assert this.ids.size() == this.byRows.size();
|
|
|
1294 |
}
|
|
|
1295 |
|
|
|
1296 |
private Where createWhere() {
|
|
|
1297 |
return new Where(this.pathFromMain.getLast().getKey(), this.ids);
|
|
|
1298 |
}
|
|
|
1299 |
}
|
|
|
1300 |
|
|
|
1301 |
private final List<SQLRowValues> fetch(MainResult mainRes, Where w, final ITransformer<SQLSelect, SQLSelect> selTransf, final Boolean unmodifiableRows) throws IllegalArgumentException {
|
|
|
1302 |
final GraftState graftState = mainRes == null ? null : mainRes.getLastGraftState();
|
|
|
1303 |
if (graftState != null) {
|
|
|
1304 |
final Where graftWhere = graftState.createWhere();
|
|
|
1305 |
if (graftWhere.equals(Where.FALSE))
|
|
|
1306 |
return Collections.emptyList();
|
|
|
1307 |
w = Where.and(w, graftWhere);
|
|
|
1308 |
}
|
93 |
ilm |
1309 |
final SQLSelect req;
|
|
|
1310 |
final Map<Path, Map<Path, SQLRowValuesListFetcher>> grafts;
|
151 |
ilm |
1311 |
final Map<Path, Path> postLinks;
|
93 |
ilm |
1312 |
final boolean freezeRows;
|
|
|
1313 |
// the only other internal state used is this.descendantPath which is final immutable
|
|
|
1314 |
synchronized (this) {
|
142 |
ilm |
1315 |
req = this.getReq(w, selTransf);
|
93 |
ilm |
1316 |
grafts = this.getGrafts();
|
151 |
ilm |
1317 |
postLinks = this.postFetchLinks;
|
93 |
ilm |
1318 |
freezeRows = unmodifiableRows == null ? this.areReturnedRowsUnmodifiable() : unmodifiableRows.booleanValue();
|
|
|
1319 |
}
|
17 |
ilm |
1320 |
// getName() would take 5% of ResultSetHandler.handle()
|
83 |
ilm |
1321 |
final List<FieldRef> selectFields = req.getSelectFields();
|
|
|
1322 |
final int selectFieldsSize = selectFields.size();
|
142 |
ilm |
1323 |
final List<String> selectFieldsNames = req.getSelectNames();
|
17 |
ilm |
1324 |
final SQLTable table = getGraph().getTable();
|
|
|
1325 |
|
|
|
1326 |
// create a flat list of the graph nodes, we just need the table, field count and the index
|
|
|
1327 |
// in this list of its linked table, eg for CPI -> LOCAL -> BATIMENT -> SITE :
|
|
|
1328 |
// <LOCAL,2,0>, <BATIMENT,2,0>, <SITE,5,1>, <CPI,4,0>
|
|
|
1329 |
final int graphSize = this.getGraph().getGraph().size();
|
|
|
1330 |
final List<GraphNode> l = new ArrayList<GraphNode>(graphSize);
|
83 |
ilm |
1331 |
// check field names only once since each row has the same fields
|
|
|
1332 |
final AtomicInteger fieldIndex = new AtomicInteger(0);
|
17 |
ilm |
1333 |
walk(0, new ITransformer<State<Integer>, Integer>() {
|
|
|
1334 |
@Override
|
|
|
1335 |
public Integer transformChecked(State<Integer> input) {
|
|
|
1336 |
final int index = l.size();
|
83 |
ilm |
1337 |
final GraphNode node = new GraphNode(input);
|
|
|
1338 |
final int stop = fieldIndex.get() + node.getFieldCount();
|
|
|
1339 |
for (int i = fieldIndex.get(); i < stop; i++) {
|
|
|
1340 |
if (i >= selectFieldsSize)
|
|
|
1341 |
throw new IllegalStateException("Fields were removed from the select");
|
|
|
1342 |
final FieldRef field = selectFields.get(i);
|
|
|
1343 |
if (!node.getTable().equals(field.getTableRef().getTable()))
|
|
|
1344 |
throw new IllegalStateException("Select field not in " + node + " : " + field);
|
|
|
1345 |
}
|
|
|
1346 |
fieldIndex.set(stop);
|
|
|
1347 |
l.add(node);
|
|
|
1348 |
// used by link index of GraphNode
|
17 |
ilm |
1349 |
return index;
|
|
|
1350 |
}
|
|
|
1351 |
});
|
83 |
ilm |
1352 |
// otherwise walk() would already have thrown an exception
|
|
|
1353 |
assert fieldIndex.get() <= selectFieldsSize;
|
|
|
1354 |
if (fieldIndex.get() != selectFieldsSize) {
|
142 |
ilm |
1355 |
throw new IllegalStateException("Items have been added to the select (which is useless, since only fields specified by rows are returned and WHERE cannot access SELECT columns) : "
|
|
|
1356 |
+ selectFields.subList(fieldIndex.get(), selectFieldsSize));
|
83 |
ilm |
1357 |
}
|
61 |
ilm |
1358 |
assert l.size() == graphSize : "All nodes weren't explored once : " + l.size() + " != " + graphSize + "\n" + this.getGraph().printGraph();
|
17 |
ilm |
1359 |
|
144 |
ilm |
1360 |
final boolean mergeReferents = this.fetchReferents();
|
93 |
ilm |
1361 |
final boolean mergeGrafts = grafts.size() > 0;
|
151 |
ilm |
1362 |
final boolean addPostLinks = !postLinks.isEmpty();
|
93 |
ilm |
1363 |
// if it is possible let the handler do the freeze, avoid another loop and further is
|
|
|
1364 |
// multi-threaded
|
151 |
ilm |
1365 |
final boolean handlerCanFreeze = !mergeReferents && !mergeGrafts && !addPostLinks;
|
93 |
ilm |
1366 |
|
25 |
ilm |
1367 |
// if we wanted to use the cache, we'd need to copy the returned list and its items (i.e.
|
|
|
1368 |
// deepCopy()), since we modify them afterwards. Or perhaps include the code after this line
|
|
|
1369 |
// into the result set handler.
|
93 |
ilm |
1370 |
final IResultSetHandler rsh = new IResultSetHandler(new RSH(selectFieldsNames, l, freezeRows && handlerCanFreeze), false);
|
21 |
ilm |
1371 |
@SuppressWarnings("unchecked")
|
25 |
ilm |
1372 |
final List<SQLRowValues> res = (List<SQLRowValues>) table.getBase().getDataSource().execute(req.asString(), rsh, false);
|
17 |
ilm |
1373 |
// e.g. list of batiment pointing to site
|
144 |
ilm |
1374 |
final List<SQLRowValues> merged;
|
|
|
1375 |
if (!mergeReferents) {
|
|
|
1376 |
merged = res;
|
|
|
1377 |
} else if (graftState == null) {
|
|
|
1378 |
merged = merge(res);
|
|
|
1379 |
} else {
|
|
|
1380 |
// merge before recursive call, so it can access the main graph
|
|
|
1381 |
merged = mergeGraft(res, graftState.byRows);
|
|
|
1382 |
}
|
93 |
ilm |
1383 |
if (mergeGrafts) {
|
144 |
ilm |
1384 |
if (mainRes == null) {
|
|
|
1385 |
mainRes = new MainResult();
|
|
|
1386 |
}
|
93 |
ilm |
1387 |
for (final Entry<Path, Map<Path, SQLRowValuesListFetcher>> graftPlaceEntry : grafts.entrySet()) {
|
17 |
ilm |
1388 |
// e.g. BATIMENT
|
|
|
1389 |
final Path graftPlace = graftPlaceEntry.getKey();
|
144 |
ilm |
1390 |
// common to all grafts to support CPI -> LOCAL -> BATIMENT and RECEPTEUR
|
17 |
ilm |
1391 |
// -> LOCAL -> BATIMENT (ie avoid duplicate LOCAL)
|
144 |
ilm |
1392 |
mainRes.push(merged, graftPlace);
|
17 |
ilm |
1393 |
for (final Entry<Path, SQLRowValuesListFetcher> e : graftPlaceEntry.getValue().entrySet()) {
|
|
|
1394 |
// e.g BATIMENT <- LOCAL <- CPI
|
|
|
1395 |
final Path descendantPath = e.getKey();
|
|
|
1396 |
assert descendantPath.getFirst() == graftPlace.getLast() : descendantPath + " != " + graftPlace;
|
|
|
1397 |
final SQLRowValuesListFetcher graft = e.getValue();
|
144 |
ilm |
1398 |
graft.fetch(mainRes, null, null, false);
|
17 |
ilm |
1399 |
}
|
144 |
ilm |
1400 |
mainRes.pop();
|
17 |
ilm |
1401 |
}
|
|
|
1402 |
}
|
151 |
ilm |
1403 |
if (addPostLinks) {
|
|
|
1404 |
// group by destinationPath to index only once for all links to add
|
|
|
1405 |
// SetMap<Tuple2<commonPath, destinationPath>, toAddPath>
|
|
|
1406 |
final SetMap<Tuple2<Path, Path>, Path> byCommonPath = new SetMap<>();
|
|
|
1407 |
for (final Entry<Path, Path> e : postLinks.entrySet()) {
|
|
|
1408 |
final Path toAdd = e.getKey();
|
|
|
1409 |
final Path existingPath = e.getValue();
|
|
|
1410 |
final Path commonPath = toAdd.getCommonPath(existingPath);
|
|
|
1411 |
byCommonPath.add(Tuple2.create(commonPath, existingPath.subPath(commonPath.length())), toAdd.subPath(commonPath.length()));
|
|
|
1412 |
}
|
|
|
1413 |
/**
|
|
|
1414 |
* <pre>
|
|
|
1415 |
* LOCAL <-- SRC <-- JOIN --> OBSERVATION
|
|
|
1416 |
* \--ID_OLDEST_OBS--/
|
|
|
1417 |
* \--ID_MOST_SERIOUS_OBS--/
|
|
|
1418 |
* </pre>
|
|
|
1419 |
*/
|
|
|
1420 |
for (final Entry<Tuple2<Path, Path>, Set<Path>> e : byCommonPath.entrySet()) {
|
|
|
1421 |
// LOCAL <-- SRC
|
|
|
1422 |
final Path commonPath = e.getKey().get0();
|
|
|
1423 |
// SRC <-- JOIN --> OBSERVATION
|
|
|
1424 |
final Path throughTargetRows = e.getKey().get1();
|
|
|
1425 |
// SRC -- ID_OLDEST_OBS --> OBSERVATION
|
|
|
1426 |
// SRC -- ID_MOST_SERIOUS_OBS --> OBSERVATION
|
|
|
1427 |
final Set<Path> pathsToAdd = e.getValue();
|
|
|
1428 |
|
|
|
1429 |
for (final SQLRowValues v : merged) {
|
|
|
1430 |
for (final SQLRowValues commonRow : v.getDistantRows(commonPath)) {
|
|
|
1431 |
// index target rows
|
|
|
1432 |
final Map<Number, SQLRowValues> byIDs = new HashMap<>();
|
|
|
1433 |
for (final SQLRowValues target : commonRow.getDistantRows(throughTargetRows)) {
|
|
|
1434 |
byIDs.put(target.getIDNumber(), target);
|
|
|
1435 |
}
|
|
|
1436 |
// add links
|
|
|
1437 |
for (final Path toAdd : pathsToAdd) {
|
|
|
1438 |
final String fkName = toAdd.getStep(-1).getSingleField().getName();
|
|
|
1439 |
// SRC
|
|
|
1440 |
final Path throughRowToUpdate = toAdd.minusLast();
|
|
|
1441 |
for (final SQLRowValues toUpdate : commonRow.getDistantRows(throughRowToUpdate)) {
|
|
|
1442 |
final Number foreignID = toUpdate.getNonEmptyForeignIDNumber(fkName);
|
|
|
1443 |
if (foreignID != null) {
|
|
|
1444 |
final SQLRowValues target = byIDs.get(foreignID);
|
|
|
1445 |
if (target == null)
|
|
|
1446 |
throw new IllegalStateException("Missing row for " + foreignID + " at " + throughTargetRows);
|
|
|
1447 |
toUpdate.put(fkName, target);
|
|
|
1448 |
}
|
|
|
1449 |
}
|
|
|
1450 |
}
|
|
|
1451 |
}
|
|
|
1452 |
}
|
|
|
1453 |
}
|
|
|
1454 |
}
|
93 |
ilm |
1455 |
if (freezeRows && !handlerCanFreeze) {
|
|
|
1456 |
for (final SQLRowValues r : merged) {
|
|
|
1457 |
r.getGraph().freeze();
|
|
|
1458 |
}
|
|
|
1459 |
}
|
17 |
ilm |
1460 |
return merged;
|
|
|
1461 |
}
|
|
|
1462 |
|
|
|
1463 |
// no need to set keep-alive too low, since on finalize() the pool shutdowns itself
|
|
|
1464 |
private static final ExecutorService exec = new ThreadPoolExecutor(0, 2, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
|
|
|
1465 |
|
|
|
1466 |
private static final class Linker implements Callable<Object> {
|
|
|
1467 |
|
|
|
1468 |
private final List<GraphNode> l;
|
|
|
1469 |
private final List<List<SQLRowValues>> rows;
|
|
|
1470 |
private final int fromIndex;
|
|
|
1471 |
private final int toIndex;
|
93 |
ilm |
1472 |
private final boolean freezeRows;
|
17 |
ilm |
1473 |
|
93 |
ilm |
1474 |
public Linker(final List<GraphNode> l, final List<List<SQLRowValues>> rows, final int first, final int last, final boolean freezeRows) {
|
17 |
ilm |
1475 |
super();
|
|
|
1476 |
this.l = l;
|
|
|
1477 |
this.rows = rows;
|
|
|
1478 |
this.fromIndex = first;
|
|
|
1479 |
this.toIndex = last;
|
93 |
ilm |
1480 |
this.freezeRows = freezeRows;
|
17 |
ilm |
1481 |
}
|
|
|
1482 |
|
|
|
1483 |
@Override
|
|
|
1484 |
public Object call() throws Exception {
|
93 |
ilm |
1485 |
link(this.l, this.rows, this.fromIndex, this.toIndex, this.freezeRows);
|
17 |
ilm |
1486 |
return null;
|
|
|
1487 |
}
|
|
|
1488 |
|
|
|
1489 |
}
|
|
|
1490 |
|
93 |
ilm |
1491 |
private static void link(final List<GraphNode> l, final List<List<SQLRowValues>> rows, final int start, final int stop, final boolean freezeRows) {
|
17 |
ilm |
1492 |
final int graphSize = l.size();
|
|
|
1493 |
for (int nodeIndex = 1; nodeIndex < graphSize; nodeIndex++) {
|
|
|
1494 |
final GraphNode node = l.get(nodeIndex);
|
|
|
1495 |
|
|
|
1496 |
final String fromName = node.getFromName();
|
|
|
1497 |
final int linkIndex = node.getLinkIndex();
|
|
|
1498 |
final boolean backwards = node.isBackwards();
|
|
|
1499 |
|
93 |
ilm |
1500 |
// freeze after the last put()
|
|
|
1501 |
final boolean freeze = freezeRows && nodeIndex == graphSize - 1;
|
|
|
1502 |
|
17 |
ilm |
1503 |
for (int i = start; i < stop; i++) {
|
|
|
1504 |
final List<SQLRowValues> row = rows.get(i);
|
|
|
1505 |
final SQLRowValues creatingVals = row.get(nodeIndex);
|
|
|
1506 |
// don't link empty values (LEFT JOIN produces rowValues filled with
|
|
|
1507 |
// nulls) to the graph
|
83 |
ilm |
1508 |
if (creatingVals != null) {
|
17 |
ilm |
1509 |
final SQLRowValues valsToFill;
|
|
|
1510 |
final SQLRowValues valsToPut;
|
|
|
1511 |
if (backwards) {
|
|
|
1512 |
valsToFill = creatingVals;
|
|
|
1513 |
valsToPut = row.get(linkIndex);
|
|
|
1514 |
} else {
|
|
|
1515 |
valsToFill = row.get(linkIndex);
|
|
|
1516 |
valsToPut = creatingVals;
|
|
|
1517 |
}
|
|
|
1518 |
|
|
|
1519 |
// check is done by updateLinks()
|
|
|
1520 |
valsToFill.put(fromName, valsToPut, false);
|
|
|
1521 |
}
|
93 |
ilm |
1522 |
// can't use creatingVals, use primary row which is never null
|
|
|
1523 |
if (freeze)
|
|
|
1524 |
row.get(0).getGraph().freeze();
|
17 |
ilm |
1525 |
}
|
|
|
1526 |
}
|
93 |
ilm |
1527 |
if (freezeRows && graphSize == 1) {
|
|
|
1528 |
for (int i = start; i < stop; i++) {
|
|
|
1529 |
final List<SQLRowValues> row = rows.get(i);
|
|
|
1530 |
final boolean justFrozen = row.get(0).getGraph().freeze();
|
|
|
1531 |
assert justFrozen : "Already frozen";
|
|
|
1532 |
}
|
|
|
1533 |
}
|
17 |
ilm |
1534 |
}
|
|
|
1535 |
|
|
|
1536 |
/**
|
|
|
1537 |
* Merge a list of fetched rowValues so that remove any duplicated rowValues. Eg, transforms
|
|
|
1538 |
* this :
|
|
|
1539 |
*
|
|
|
1540 |
* <pre>
|
|
|
1541 |
* BATIMENT[2] LOCAL[2] CPI_BT[3]
|
|
|
1542 |
* BATIMENT[2] LOCAL[2] CPI_BT[2]
|
|
|
1543 |
* BATIMENT[2] LOCAL[3]
|
|
|
1544 |
* BATIMENT[2] LOCAL[5] CPI_BT[5]
|
|
|
1545 |
* BATIMENT[3] LOCAL[4] CPI_BT[4]
|
|
|
1546 |
* BATIMENT[4]
|
|
|
1547 |
* </pre>
|
|
|
1548 |
*
|
|
|
1549 |
* into this :
|
|
|
1550 |
*
|
|
|
1551 |
* <pre>
|
|
|
1552 |
* BATIMENT[2] LOCAL[2] CPI_BT[3]
|
|
|
1553 |
* CPI_BT[2]
|
|
|
1554 |
* LOCAL[3]
|
|
|
1555 |
* LOCAL[5] CPI_BT[5]
|
|
|
1556 |
* BATIMENT[3] LOCAL[4] CPI_BT[4]
|
|
|
1557 |
* BATIMENT[4]
|
|
|
1558 |
* </pre>
|
|
|
1559 |
*
|
|
|
1560 |
* @param l a list of fetched rowValues.
|
|
|
1561 |
* @return a smaller list in which all rowValues are unique.
|
|
|
1562 |
*/
|
|
|
1563 |
private final List<SQLRowValues> merge(final List<SQLRowValues> l) {
|
93 |
ilm |
1564 |
return merge(l, l, null, this.descendantPath);
|
17 |
ilm |
1565 |
}
|
|
|
1566 |
|
144 |
ilm |
1567 |
private final List<SQLRowValues> mergeGraft(final List<SQLRowValues> l, final ListMap<Tuple2<Path, Number>, SQLRowValues> graftPlaceRows) {
|
|
|
1568 |
if (graftPlaceRows == null)
|
|
|
1569 |
throw new IllegalArgumentException("Missing map");
|
|
|
1570 |
return merge(null, l, graftPlaceRows, this.descendantPath);
|
|
|
1571 |
}
|
|
|
1572 |
|
17 |
ilm |
1573 |
/**
|
|
|
1574 |
* Merge a list of rowValues and optionally graft it onto another one.
|
|
|
1575 |
*
|
|
|
1576 |
* @param tree the list receiving the graft.
|
|
|
1577 |
* @param graft the list being merged and optionally grafted on <code>tree</code>, can be the
|
|
|
1578 |
* same as <code>tree</code>.
|
|
|
1579 |
* @param graftPlaceRows if this is a graft the destination rowValues, otherwise
|
|
|
1580 |
* <code>null</code>, this instance will be modified.
|
|
|
1581 |
* @param descendantPath the path to merge.
|
144 |
ilm |
1582 |
* @return the merged list of main values, or the graft places if it's a graft.
|
17 |
ilm |
1583 |
*/
|
132 |
ilm |
1584 |
static private final List<SQLRowValues> merge(final List<SQLRowValues> tree, final List<SQLRowValues> graft, final ListMap<Tuple2<Path, Number>, SQLRowValues> graftPlaceRows,
|
|
|
1585 |
Path descendantPath) {
|
17 |
ilm |
1586 |
final boolean isGraft = graftPlaceRows != null;
|
|
|
1587 |
assert (tree != graft) == isGraft : "Trying to graft onto itself";
|
144 |
ilm |
1588 |
final Collection<SQLRowValues> res = isGraft ? new LinkedIdentitySet<SQLRowValues>() : new ArrayList<SQLRowValues>();
|
17 |
ilm |
1589 |
// so that every graft is actually grafted onto the tree
|
83 |
ilm |
1590 |
final ListMap<Tuple2<Path, Number>, SQLRowValues> map = isGraft ? graftPlaceRows : createCollectionMap();
|
17 |
ilm |
1591 |
|
|
|
1592 |
final int stop = descendantPath.length();
|
|
|
1593 |
for (final SQLRowValues v : graft) {
|
|
|
1594 |
boolean doAdd = true;
|
61 |
ilm |
1595 |
SQLRowValues previous = null;
|
|
|
1596 |
for (int i = stop; i >= 0 && doAdd; i--) {
|
17 |
ilm |
1597 |
final Path subPath = descendantPath.subPath(0, i);
|
|
|
1598 |
final SQLRowValues desc = v.followPath(subPath);
|
|
|
1599 |
if (desc != null) {
|
|
|
1600 |
final Tuple2<Path, Number> row = Tuple2.create(subPath, desc.getIDNumber());
|
|
|
1601 |
if (map.containsKey(row)) {
|
|
|
1602 |
doAdd = false;
|
83 |
ilm |
1603 |
assert map.get(row).get(0).getFields().containsAll(desc.getFields()) : "Discarding an SQLRowValues with more fields : " + desc;
|
17 |
ilm |
1604 |
// previous being null can happen when 2 grafted paths share some steps at
|
|
|
1605 |
// the start, e.g. SOURCE -> LOCAL and CPI -> LOCAL with a LOCAL having a
|
|
|
1606 |
// SOURCE but no CPI
|
|
|
1607 |
if (previous != null) {
|
83 |
ilm |
1608 |
final List<SQLRowValues> destinationRows = map.get(row);
|
17 |
ilm |
1609 |
final int destinationSize = destinationRows.size();
|
|
|
1610 |
assert destinationSize > 0 : "Map contains row but have no corresponding value: " + row;
|
93 |
ilm |
1611 |
final String ffName = descendantPath.getSingleField(i).getName();
|
67 |
ilm |
1612 |
// avoid the first deepCopy() (needed since rows of 'previous' have
|
|
|
1613 |
// already been added to 'map') and copy before merging
|
17 |
ilm |
1614 |
for (int j = 1; j < destinationSize; j++) {
|
67 |
ilm |
1615 |
final SQLRowValues previousCopy = previous.deepCopy().put(ffName, destinationRows.get(j));
|
|
|
1616 |
// put the copied rowValues into 'map' otherwise they'd be
|
|
|
1617 |
// unreachable and thus couldn't have referents. Tested by
|
|
|
1618 |
// SQLRowValuesListFetcherTest.testSameReferentMergedMultipleTimes()
|
|
|
1619 |
// i+1 since we start from 'previous' not 'desc'
|
|
|
1620 |
for (int k = stop; k >= i + 1; k--) {
|
|
|
1621 |
final SQLRowValues descCopy = previousCopy.followPath(descendantPath.subPath(i + 1, k));
|
|
|
1622 |
if (descCopy != null) {
|
|
|
1623 |
final Tuple2<Path, Number> rowCopy = Tuple2.create(descendantPath.subPath(0, k), descCopy.getIDNumber());
|
|
|
1624 |
assert map.containsKey(rowCopy) : "Since we already iterated with i";
|
83 |
ilm |
1625 |
map.add(rowCopy, descCopy);
|
67 |
ilm |
1626 |
}
|
|
|
1627 |
}
|
17 |
ilm |
1628 |
}
|
67 |
ilm |
1629 |
// don't call map.put() it has already been handled below
|
17 |
ilm |
1630 |
previous.put(ffName, destinationRows.get(0));
|
144 |
ilm |
1631 |
|
|
|
1632 |
if (isGraft) {
|
|
|
1633 |
final Path pathToGraftPlace = subPath.reverse();
|
|
|
1634 |
for (final SQLRowValues r : destinationRows) {
|
|
|
1635 |
final SQLRowValues graftPlaceRow = r.followPath(pathToGraftPlace);
|
|
|
1636 |
if (graftPlaceRow == null)
|
|
|
1637 |
throw new IllegalStateException("Row at graft place not found");
|
|
|
1638 |
res.add(graftPlaceRow);
|
|
|
1639 |
}
|
|
|
1640 |
}
|
17 |
ilm |
1641 |
}
|
|
|
1642 |
} else {
|
83 |
ilm |
1643 |
map.add(row, desc);
|
17 |
ilm |
1644 |
}
|
|
|
1645 |
previous = desc;
|
|
|
1646 |
}
|
|
|
1647 |
}
|
|
|
1648 |
if (doAdd) {
|
|
|
1649 |
assert !isGraft : "Adding graft values as tree values";
|
|
|
1650 |
res.add(v);
|
|
|
1651 |
}
|
|
|
1652 |
}
|
144 |
ilm |
1653 |
return res instanceof List ? (List<SQLRowValues>) res : new ArrayList<>(res);
|
17 |
ilm |
1654 |
}
|
|
|
1655 |
|
|
|
1656 |
@Override
|
|
|
1657 |
public String toString() {
|
132 |
ilm |
1658 |
return this.getClass().getSimpleName() + " for " + this.getGraph() + " with " + this.getSelID() + " and " + this.getSelectTransformers();
|
17 |
ilm |
1659 |
}
|
|
|
1660 |
|
|
|
1661 |
@Override
|
|
|
1662 |
public boolean equals(Object obj) {
|
|
|
1663 |
if (obj instanceof SQLRowValuesListFetcher) {
|
|
|
1664 |
final SQLRowValuesListFetcher o = (SQLRowValuesListFetcher) obj;
|
93 |
ilm |
1665 |
final SQLSelect thisReq, oReq;
|
|
|
1666 |
final Map<Path, Map<Path, SQLRowValuesListFetcher>> thisGrafts, oGrafts;
|
|
|
1667 |
synchronized (this) {
|
|
|
1668 |
thisReq = this.getReq();
|
|
|
1669 |
thisGrafts = this.getGrafts();
|
|
|
1670 |
}
|
|
|
1671 |
synchronized (o) {
|
|
|
1672 |
oReq = o.getReq();
|
|
|
1673 |
oGrafts = o.getGrafts();
|
|
|
1674 |
}
|
17 |
ilm |
1675 |
// use getReq() to avoid selTransf equality pb (ie we generally use anonymous classes
|
|
|
1676 |
// which thus lack equals())
|
93 |
ilm |
1677 |
return thisReq.equals(oReq) && CompareUtils.equals(this.descendantPath, o.descendantPath) && thisGrafts.equals(oGrafts);
|
17 |
ilm |
1678 |
} else
|
|
|
1679 |
return false;
|
|
|
1680 |
}
|
|
|
1681 |
|
|
|
1682 |
@Override
|
|
|
1683 |
public int hashCode() {
|
|
|
1684 |
return this.getReq().hashCode();
|
|
|
1685 |
}
|
|
|
1686 |
}
|