Line 121... |
Line 121... |
121 |
|
121 |
|
122 |
private final DBSystemRoot getSystemRoot() {
|
122 |
private final DBSystemRoot getSystemRoot() {
|
123 |
return this.getHead().getTable().getDBSystemRoot();
|
123 |
return this.getHead().getTable().getDBSystemRoot();
|
124 |
}
|
124 |
}
|
125 |
|
125 |
|
- |
|
126 |
public final int getLinksCount() {
|
- |
|
127 |
return this.links.size();
|
- |
|
128 |
}
|
- |
|
129 |
|
126 |
/**
|
130 |
/**
|
127 |
* All the rowValues in this cluster.
|
131 |
* All the rowValues in this cluster.
|
128 |
*
|
132 |
*
|
129 |
* @return the set of SQLRowValues.
|
133 |
* @return the set of SQLRowValues.
|
130 |
*/
|
134 |
*/
|
Line 323... |
Line 327... |
323 |
final SQLRowValues deepCopy(SQLRowValues v, final boolean freeze) {
|
327 |
final SQLRowValues deepCopy(SQLRowValues v, final boolean freeze) {
|
324 |
return deepCopy(freeze).get(v);
|
328 |
return deepCopy(freeze).get(v);
|
325 |
}
|
329 |
}
|
326 |
|
330 |
|
327 |
public final Map<SQLRowValues, SQLRowValues> deepCopy(final boolean freeze) {
|
331 |
public final Map<SQLRowValues, SQLRowValues> deepCopy(final boolean freeze) {
|
- |
|
332 |
return this.copy(null, false, freeze);
|
- |
|
333 |
}
|
- |
|
334 |
|
- |
|
335 |
/**
|
- |
|
336 |
* Copy a subset of this graph. For each link to copy, if the destination was copied then the
|
- |
|
337 |
* new row will point to it, otherwise the new row will point to the original row. For example,
|
- |
|
338 |
* if
|
- |
|
339 |
*
|
- |
|
340 |
* <pre>
|
- |
|
341 |
* start is CONTAINER <-- *ITEM [F1, F2]* --> PRIVATE
|
- |
|
342 |
* and graph is ITEM [F2, ID_PRIVATE]
|
- |
|
343 |
* then after this method :
|
- |
|
344 |
* CONTAINER <-- ITEM [F1, F2] --> PRIVATE
|
- |
|
345 |
* \-- ITEM [F2] --> PRIVATE
|
- |
|
346 |
* </pre>
|
- |
|
347 |
*
|
- |
|
348 |
* @param start where to start copying.
|
- |
|
349 |
* @param graph which rows and which fields to copy, not <code>null</code>.
|
- |
|
350 |
* @return the new rows, indexed by the original rows in this instance.
|
- |
|
351 |
*/
|
- |
|
352 |
public final Map<SQLRowValues, SQLRowValues> copy(final SQLRowValues start, final SQLRowValues graph) {
|
- |
|
353 |
return this.copy(computeToRetain(start, graph, true), true, false);
|
- |
|
354 |
}
|
- |
|
355 |
|
- |
|
356 |
/**
|
- |
|
357 |
* Copy rows of this graph.
|
- |
|
358 |
*
|
- |
|
359 |
* @param subset which rows and which fields to copy, <code>null</code> to copy all rows.
|
- |
|
360 |
* @param allowSameGraph used when copied rows point to rows which weren't copied,
|
- |
|
361 |
* <code>true</code> to point to the original rows (and thus linking the new rows into
|
- |
|
362 |
* the existing graph), <code>false</code> to flatten the link (like
|
- |
|
363 |
* {@link ForeignCopyMode#COPY_ID_OR_RM}).
|
- |
|
364 |
* @param freeze <code>true</code> if the copied rows should be frozen.
|
- |
|
365 |
* @return the new rows, indexed by the original rows in this instance.
|
- |
|
366 |
*/
|
- |
|
367 |
private final Map<SQLRowValues, SQLRowValues> copy(final SetMap<SQLRowValues, String> subset, final boolean allowSameGraph, final boolean freeze) {
|
- |
|
368 |
assert !allowSameGraph || !freeze;
|
328 |
// copy all rowValues of this graph
|
369 |
// copy all rowValues of this graph
|
329 |
final Map<SQLRowValues, SQLRowValues> noLinkCopy = new IdentityHashMap<SQLRowValues, SQLRowValues>();
|
370 |
final Map<SQLRowValues, SQLRowValues> noLinkCopy = new IdentityHashMap<SQLRowValues, SQLRowValues>();
|
330 |
// don't copy foreigns, but we want to preserve the order of all fields. This works because
|
371 |
// don't copy foreigns, but we want to preserve the order of all fields. This works because
|
331 |
// the second put() with the actual foreign row doesn't change the order.
|
372 |
// the second put() with the actual foreign row doesn't change the order.
|
332 |
final ForeignCopyMode copyMode = ForeignCopyMode.COPY_NULL;
|
373 |
final ForeignCopyMode copyMode = ForeignCopyMode.COPY_NULL;
|
333 |
for (final SQLRowValues n : this.getItems()) {
|
374 |
for (final SQLRowValues n : this.getItems()) {
|
334 |
final SQLRowValues copy;
|
375 |
final SQLRowValues copy;
|
- |
|
376 |
if (subset == null || subset.containsKey(n)) {
|
335 |
// might as well use the minimum memory if the values won't change
|
377 |
// might as well use the minimum memory if the values won't change
|
336 |
if (freeze) {
|
378 |
if (freeze) {
|
337 |
copy = new SQLRowValues(n.getTable(), n.size(), n.getForeignsSize(), n.getReferents().size());
|
379 |
copy = new SQLRowValues(n.getTable(), n.size(), n.getForeignsSize(), n.getReferents().size());
|
338 |
copy.setAll(n.getAllValues(copyMode));
|
380 |
copy.setAll(n.getAllValues(copyMode));
|
339 |
} else {
|
381 |
} else {
|
340 |
copy = new SQLRowValues(n, copyMode);
|
382 |
copy = new SQLRowValues(n, copyMode);
|
- |
|
383 |
if (subset != null) {
|
- |
|
384 |
copy.retainAll(subset.get(n));
|
- |
|
385 |
}
|
341 |
}
|
386 |
}
|
342 |
noLinkCopy.put(n, copy);
|
387 |
noLinkCopy.put(n, copy);
|
343 |
}
|
388 |
}
|
- |
|
389 |
}
|
344 |
|
390 |
|
345 |
// and link them together in order
|
391 |
// and link them together in order
|
- |
|
392 |
final List<Link> iterableLinks = subset == null ? this.links : new ArrayList<Link>(this.links);
|
346 |
for (final Link l : this.links) {
|
393 |
for (final Link l : iterableLinks) {
|
347 |
if (l.getField() != null) {
|
394 |
if (l.getField() != null) {
|
- |
|
395 |
final SQLRowValues sourceCopy = noLinkCopy.get(l.getSrc());
|
- |
|
396 |
if (subset == null || (sourceCopy != null && sourceCopy.contains(l.getField().getName()))) {
|
- |
|
397 |
assert l.getDest() != null;
|
348 |
noLinkCopy.get(l.getSrc()).put(l.getField().getName(), noLinkCopy.get(l.getDest()));
|
398 |
final SQLRowValues destCopy = noLinkCopy.get(l.getDest());
|
- |
|
399 |
final Object dest;
|
- |
|
400 |
if (destCopy != null)
|
- |
|
401 |
dest = destCopy;
|
- |
|
402 |
else if (allowSameGraph)
|
- |
|
403 |
dest = l.getDest();
|
- |
|
404 |
else if (l.getDest().hasID())
|
- |
|
405 |
dest = l.getDest().getIDNumber();
|
- |
|
406 |
else
|
- |
|
407 |
dest = null;
|
- |
|
408 |
if (dest != null) {
|
- |
|
409 |
sourceCopy.put(l.getField().getName(), dest);
|
- |
|
410 |
} else {
|
- |
|
411 |
// ForeignCopyMode.COPY_ID_OR_RM like pruneWithoutCopy() (avoids
|
- |
|
412 |
// leaving nulls)
|
- |
|
413 |
sourceCopy.remove(l.getField().getName());
|
- |
|
414 |
}
|
- |
|
415 |
}
|
349 |
} else {
|
416 |
} else {
|
350 |
assert noLinkCopy.containsKey(l.getSrc());
|
417 |
assert subset != null || noLinkCopy.containsKey(l.getSrc());
|
351 |
}
|
418 |
}
|
352 |
}
|
419 |
}
|
353 |
|
420 |
|
354 |
final SQLRowValues res = noLinkCopy.values().iterator().next();
|
421 |
final SQLRowValues res = noLinkCopy.values().iterator().next();
|
355 |
// only force graph creation if needed
|
422 |
// only force graph creation if needed
|
356 |
if (freeze)
|
423 |
if (freeze)
|
357 |
res.getGraph().freeze();
|
424 |
res.getGraph().freeze();
|
358 |
assert res.isFrozen() == freeze;
|
425 |
assert res.isFrozen() == freeze;
|
- |
|
426 |
assert allowSameGraph || res.getGraph() != this;
|
359 |
|
427 |
|
360 |
return noLinkCopy;
|
428 |
return noLinkCopy;
|
361 |
}
|
429 |
}
|
362 |
|
430 |
|
363 |
public final StoreResult insert() throws SQLException {
|
431 |
public final StoreResult insert() throws SQLException {
|
Line 894... |
Line 962... |
894 |
*/
|
962 |
*/
|
895 |
public final SQLRowValues prune(final SQLRowValues start, final SQLRowValues graph, final boolean keepUnionOfFields) {
|
963 |
public final SQLRowValues prune(final SQLRowValues start, final SQLRowValues graph, final boolean keepUnionOfFields) {
|
896 |
return pruneMap(start, graph, keepUnionOfFields).get(start);
|
964 |
return pruneMap(start, graph, keepUnionOfFields).get(start);
|
897 |
}
|
965 |
}
|
898 |
|
966 |
|
899 |
// private since result isn't trimmed, the values are still all there, not all in the pruned
|
- |
|
900 |
// graph
|
- |
|
901 |
private final Map<SQLRowValues, SQLRowValues> pruneMap(final SQLRowValues start, final SQLRowValues graph, final boolean keepUnionOfFields) {
|
967 |
public final Map<SQLRowValues, SQLRowValues> pruneMap(final SQLRowValues start, final SQLRowValues graph, final boolean keepUnionOfFields) {
|
902 |
this.containsCheck(start);
|
968 |
this.containsCheck(start);
|
- |
|
969 |
return this.copy(computeToRetain(start, graph, keepUnionOfFields), false, false);
|
- |
|
970 |
}
|
- |
|
971 |
|
- |
|
972 |
static SetMap<SQLRowValues, String> computeToRetain(final SQLRowValues start, final SQLRowValues graph, final boolean keepUnionOfFields) {
|
903 |
if (!start.getTable().equals(graph.getTable()))
|
973 |
if (!start.getTable().equals(graph.getTable()))
|
904 |
throw new IllegalArgumentException(start + " is not from the same table as " + graph);
|
974 |
throw new IllegalArgumentException(start + " is not from the same table as " + graph);
|
905 |
// there's no way to tell apart 2 referents
|
975 |
// there's no way to tell apart 2 referents
|
906 |
if (!graph.getGraph().hasOneRowPerPath())
|
976 |
if (!graph.getGraph().hasOneRowPerPath())
|
907 |
throw new IllegalArgumentException("More than one row for " + graph.printGraph());
|
977 |
throw new IllegalArgumentException("More than one row for " + graph.printGraph());
|
908 |
|
978 |
|
909 |
final Map<SQLRowValues, SQLRowValues> map = start.getGraph().deepCopy(false);
|
- |
|
910 |
final SQLRowValues res = map.get(start);
|
- |
|
911 |
|
- |
|
912 |
final SetMap<SQLRowValues, String> toRetain = new SetMap<SQLRowValues, String>(new IdentityHashMap<SQLRowValues, Set<String>>(), Mode.NULL_FORBIDDEN);
|
979 |
final SetMap<SQLRowValues, String> toRetain = new SetMap<SQLRowValues, String>(new IdentityHashMap<SQLRowValues, Set<String>>(), Mode.NULL_FORBIDDEN);
|
913 |
// BREADTH_FIRST to stop as soon as this no longer have rows in the graph
|
980 |
// BREADTH_FIRST to stop as soon as this no longer have rows in the graph
|
914 |
// CycleAllowed since we need to go through every path (e.g. what is a cycle in graph might
|
981 |
// CycleAllowed since we need to go through every path (e.g. what is a cycle in graph might
|
915 |
// not be in this :
|
982 |
// not be in this :
|
916 |
// SITE1 -> CONTACT1 -> SITE1 for graph and
|
983 |
// SITE1 -> CONTACT1 -> SITE1 for graph and
|
Line 919... |
Line 986... |
919 |
graph.getGraph().walk(graph, null, new ITransformer<State<Object>, Object>() {
|
986 |
graph.getGraph().walk(graph, null, new ITransformer<State<Object>, Object>() {
|
920 |
@Override
|
987 |
@Override
|
921 |
public Object transformChecked(State<Object> input) {
|
988 |
public Object transformChecked(State<Object> input) {
|
922 |
final SQLRowValues r = input.getCurrent();
|
989 |
final SQLRowValues r = input.getCurrent();
|
923 |
// since we allowed cycles in graph, allow it here
|
990 |
// since we allowed cycles in graph, allow it here
|
924 |
final Collection<SQLRowValues> rows = res.followPath(input.getPath(), CreateMode.CREATE_NONE, false, true);
|
991 |
final Collection<SQLRowValues> rows = start.followPath(input.getPath(), CreateMode.CREATE_NONE, false, true);
|
925 |
// since we're using BREADTH_FIRST, the next path will be longer so no need to
|
992 |
// since we're using BREADTH_FIRST, the next path will be longer so no need to
|
926 |
// continue if there's already no row
|
993 |
// continue if there's already no row
|
927 |
if (rows.isEmpty())
|
994 |
if (rows.isEmpty())
|
928 |
throw new StopRecurseException().setCompletely(false);
|
995 |
throw new StopRecurseException().setCompletely(false);
|
929 |
for (final SQLRowValues row : rows) {
|
996 |
for (final SQLRowValues row : rows) {
|
Line 933... |
Line 1000... |
933 |
toRetain.getCollection(row).retainAll(r.getFields());
|
1000 |
toRetain.getCollection(row).retainAll(r.getFields());
|
934 |
}
|
1001 |
}
|
935 |
return null;
|
1002 |
return null;
|
936 |
}
|
1003 |
}
|
937 |
}, walkOptions);
|
1004 |
}, walkOptions);
|
- |
|
1005 |
return toRetain;
|
- |
|
1006 |
}
|
- |
|
1007 |
|
- |
|
1008 |
public final SQLRowValues pruneWithoutCopy(final SQLRowValues start, final SQLRowValues graph) {
|
- |
|
1009 |
return this.pruneWithoutCopy(start, graph, true);
|
- |
|
1010 |
}
|
- |
|
1011 |
|
- |
|
1012 |
public final SQLRowValues pruneWithoutCopy(final SQLRowValues start, final SQLRowValues graph, final boolean keepUnionOfFields) {
|
- |
|
1013 |
this.containsCheck(start);
|
- |
|
1014 |
final SetMap<SQLRowValues, String> toRetain = computeToRetain(start, graph, keepUnionOfFields);
|
- |
|
1015 |
assert toRetain.containsKey(start);
|
938 |
|
1016 |
|
939 |
// remove extra fields and flatten if necessary
|
1017 |
// remove extra fields and flatten if necessary
|
940 |
for (final Entry<SQLRowValues, Set<String>> e : toRetain.entrySet()) {
|
1018 |
for (final Entry<SQLRowValues, Set<String>> e : toRetain.entrySet()) {
|
941 |
final SQLRowValues r = e.getKey();
|
1019 |
final SQLRowValues r = e.getKey();
|
942 |
r.retainAll(e.getValue());
|
1020 |
r.retainAll(e.getValue());
|
Line 950... |
Line 1028... |
950 |
for (final String fieldToFlatten : toFlatten) {
|
1028 |
for (final String fieldToFlatten : toFlatten) {
|
951 |
r.flatten(fieldToFlatten, ForeignCopyMode.COPY_ID_OR_RM);
|
1029 |
r.flatten(fieldToFlatten, ForeignCopyMode.COPY_ID_OR_RM);
|
952 |
}
|
1030 |
}
|
953 |
}
|
1031 |
}
|
954 |
// now, remove referents that aren't in the graph
|
1032 |
// now, remove referents that aren't in the graph
|
955 |
for (final SQLRowValues r : new ArrayList<SQLRowValues>(res.getGraph().getItems())) {
|
1033 |
for (final SQLRowValues r : new ArrayList<SQLRowValues>(start.getGraph().getItems())) {
|
956 |
if (!toRetain.containsKey(r)) {
|
1034 |
if (!toRetain.containsKey(r)) {
|
957 |
// only remove links at the border and even then, only remove links to the result :
|
1035 |
// only remove links at the border and even then, only remove links to the result :
|
958 |
// avoid creating a myriad of graphs
|
1036 |
// avoid creating a myriad of graphs
|
959 |
final Set<String> toFlatten = new HashSet<String>();
|
1037 |
final Set<String> toFlatten = new HashSet<String>();
|
960 |
for (final Entry<String, SQLRowValues> e2 : r.getForeigns().entrySet()) {
|
1038 |
for (final Entry<String, SQLRowValues> e2 : r.getForeigns().entrySet()) {
|
Line 964... |
Line 1042... |
964 |
}
|
1042 |
}
|
965 |
}
|
1043 |
}
|
966 |
r.removeAll(toFlatten);
|
1044 |
r.removeAll(toFlatten);
|
967 |
}
|
1045 |
}
|
968 |
}
|
1046 |
}
|
969 |
assert res.getGraph().getItems().equals(toRetain.keySet());
|
1047 |
assert start.getGraph().getItems().equals(toRetain.keySet());
|
970 |
|
- |
|
- |
|
1048 |
// NOTE this instance no longer the graph of start if referents were removed
|
971 |
return map;
|
1049 |
return start;
|
972 |
}
|
1050 |
}
|
973 |
|
1051 |
|
974 |
// TODO handle referents (and decide how to handle multiple paths to the same node)
|
1052 |
// TODO handle referents (and decide how to handle multiple paths to the same node)
|
975 |
final void grow(final SQLRowValues start, final SQLRowValues toGrow, final boolean checkFields) {
|
1053 |
final void grow(final SQLRowValues start, final SQLRowValues toGrow, final boolean checkFields, final boolean growUndefined) {
|
976 |
this.containsCheck(start);
|
1054 |
this.containsCheck(start);
|
977 |
if (!start.getTable().equals(toGrow.getTable()))
|
1055 |
if (!start.getTable().equals(toGrow.getTable()))
|
978 |
throw new IllegalArgumentException(start + " is not from the same table as " + toGrow);
|
1056 |
throw new IllegalArgumentException(start + " is not from the same table as " + toGrow);
|
979 |
this.walk(start, null, new ITransformer<State<Object>, Object>() {
|
1057 |
this.walk(start, toGrow, new ITransformer<State<SQLRowValues>, SQLRowValues>() {
|
980 |
@Override
|
1058 |
@Override
|
981 |
public Object transformChecked(State<Object> input) {
|
1059 |
public SQLRowValues transformChecked(State<SQLRowValues> input) {
|
982 |
final SQLRowValues existing = toGrow.followPath(input.getPath());
|
1060 |
final SQLRowValues existing = toGrow.followPath(input.getPath());
|
- |
|
1061 |
// don't add undefined row if there's none or if don't want
|
- |
|
1062 |
if (existing == null && input.getAcc().isForeignEmpty(input.getFrom().getName()) && (input.getPath().getLast().getUndefinedIDNumber() == null || !growUndefined))
|
- |
|
1063 |
throw new StopRecurseException().setCompletely(false);
|
983 |
if (existing == null || (checkFields && !existing.getFields().containsAll(input.getCurrent().getFields()))) {
|
1064 |
if (existing == null || (checkFields && !existing.getFields().containsAll(input.getCurrent().getFields()))) {
|
984 |
final SQLRowValues leaf = toGrow.assurePath(input.getPath());
|
1065 |
final SQLRowValues leaf = toGrow.assurePath(input.getPath());
|
985 |
if (leaf.hasID()) {
|
1066 |
if (leaf.hasID()) {
|
986 |
final SQLRowValuesListFetcher fetcher = new SQLRowValuesListFetcher(input.getCurrent());
|
1067 |
final SQLRowValuesListFetcher fetcher = new SQLRowValuesListFetcher(input.getCurrent());
|
- |
|
1068 |
// don't exclude undef otherwise cannot grow eg
|
- |
|
1069 |
// LOCAL.ID_FAMILLE_2 = 1
|
- |
|
1070 |
if (growUndefined) {
|
987 |
fetcher.setSelTransf(new ITransformer<SQLSelect, SQLSelect>() {
|
1071 |
fetcher.setSelTransf(new ITransformer<SQLSelect, SQLSelect>() {
|
988 |
@Override
|
1072 |
@Override
|
989 |
public SQLSelect transformChecked(SQLSelect input) {
|
1073 |
public SQLSelect transformChecked(SQLSelect input) {
|
990 |
// don't exclude undef otherwise cannot grow eg
|
- |
|
991 |
// LOCAL.ID_FAMILLE_2 = 1
|
- |
|
992 |
input.setExcludeUndefined(false);
|
1074 |
input.setExcludeUndefined(false);
|
993 |
return input;
|
1075 |
return input;
|
994 |
}
|
1076 |
}
|
995 |
});
|
1077 |
});
|
- |
|
1078 |
}
|
996 |
final SQLRowValues fetched = fetcher.fetchOne(leaf.getIDNumber());
|
1079 |
final SQLRowValues fetched = fetcher.fetchOne(leaf.getIDNumber());
|
997 |
if (fetched == null)
|
1080 |
if (fetched == null)
|
998 |
throw new IllegalArgumentException("no row for " + fetcher);
|
1081 |
throw new IllegalArgumentException("no row for " + fetcher);
|
999 |
leaf.load(fetched, null);
|
1082 |
leaf.load(fetched, null);
|
- |
|
1083 |
// we already loaded all further rows
|
- |
|
1084 |
throw new StopRecurseException().setCompletely(false);
|
1000 |
} else
|
1085 |
} else {
|
1001 |
throw new IllegalArgumentException("cannot expand, missing ID in " + leaf + " at " + input.getPath());
|
1086 |
throw new IllegalArgumentException("cannot expand, missing ID in " + leaf + " at " + input.getPath());
|
1002 |
}
|
1087 |
}
|
- |
|
1088 |
} else {
|
1003 |
return null;
|
1089 |
return existing;
|
- |
|
1090 |
}
|
1004 |
}
|
1091 |
}
|
1005 |
}, RecursionType.BREADTH_FIRST);
|
1092 |
}, RecursionType.BREADTH_FIRST, Direction.FOREIGN);
|
1006 |
}
|
1093 |
}
|
1007 |
|
1094 |
|
1008 |
public final String contains(final SQLRowValues start, SQLRowValues graph) {
|
1095 |
public final String contains(final SQLRowValues start, SQLRowValues graph) {
|
1009 |
return this.contains(start, graph, true);
|
1096 |
return this.contains(start, graph, true);
|
1010 |
}
|
1097 |
}
|