Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name := "overflowdb2"
ThisBuild / organization := "io.appthreat"
ThisBuild / version := "3.0.2"
ThisBuild / version := "3.0.3"
ThisBuild / scalaVersion := "3.8.4"
publish / skip := true

Expand Down
54 changes: 53 additions & 1 deletion core/src/main/java/overflowdb/storage/GraphFragmentCodec.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@ public static Optional<byte[]> encode(BatchedUpdate.DiffOrBuilder diff, Boundary
}
}
}
// Also include detached nodes that appear only as edge endpoints (never explicitly added as
// a node change). applyDiff materializes such endpoints, so they are genuinely part of the
// fragment; without this they would look "external" and make an otherwise self-contained
// fragment be refused (e.g. JS frontends reference BINDING/LOCAL nodes only via edges).
for (Iterator<BatchedUpdate.Change> it = diff.iterator(); it.hasNext(); ) {
BatchedUpdate.Change change = it.next();
if (change instanceof BatchedUpdate.CreateEdge) {
BatchedUpdate.CreateEdge create = (BatchedUpdate.CreateEdge) change;
registerDetachedEndpoint(create.src, nodeToLocalId, localNodes);
registerDetachedEndpoint(create.dst, nodeToLocalId, localNodes);
}
}

List<String> glossaryList = new ArrayList<>();
Map<String, Integer> glossaryMap = new HashMap<>();
Expand Down Expand Up @@ -90,7 +102,10 @@ public static Optional<byte[]> encode(BatchedUpdate.DiffOrBuilder diff, Boundary
srcLocal = nodeToLocalId.get(((NodeRef<?>) create.src).get());
}
if (srcLocal == null) {
continue;
// Edge from a node outside this fragment: the fragment is not self-contained and
// would be lossy. Refuse to encode so the caller recomputes rather than caching a
// graph with silently dropped edges.
return Optional.empty();
}

Integer dstLocal = nodeToLocalId.get(create.dst);
Expand All @@ -108,6 +123,9 @@ public static Optional<byte[]> encode(BatchedUpdate.DiffOrBuilder diff, Boundary
}
if (keyOpt != null && keyOpt.isPresent()) {
boundaryRefs.add(new DecodedFragment.DecodedBoundaryRef(srcLocal, create.label, keyOpt.get()));
} else {
// Dangling edge to an external node with no boundary key: not self-contained.
return Optional.empty();
}
}
}
Expand Down Expand Up @@ -518,11 +536,45 @@ private static void packTypedValue(MessageBufferPacker packer, Object value, Map
boolean[] array = (boolean[]) value;
packer.packArrayHeader(array.length);
for (boolean b : array) packer.packBoolean(b);
} else if (value.getClass().getName().startsWith("scala.collection.")) {
// Scala collection-valued properties (e.g. list-typed CPG properties such as
// DYNAMIC_TYPE_HINT_FULL_NAME) are packed element-wise as an object array, matching how
// the graph store handles list properties. odb2 has no compile-time scala dependency, so
// the collection is iterated reflectively.
java.util.List<Object> list = scalaCollectionToList(value);
packer.packByte(ValueTypes.ARRAY_OBJECT.id);
packer.packArrayHeader(list.size());
for (Object o : list) packTypedValue(packer, o, nodeToLocalId, getGlossaryId);
} else {
throw new UnsupportedOperationException("value of type " + value.getClass() + " not supported for serialization");
}
}

/** Register a detached edge endpoint as a fragment-local node if it is a DetachedNodeData that
* was not already collected (NodeRef endpoints are existing graph nodes - left external). */
private static void registerDetachedEndpoint(NodeOrDetachedNode endpoint, Map<Object, Integer> nodeToLocalId, List<DetachedNodeData> localNodes) {
if (endpoint instanceof DetachedNodeData && !nodeToLocalId.containsKey(endpoint)) {
nodeToLocalId.put(endpoint, localNodes.size());
localNodes.add((DetachedNodeData) endpoint);
}
}

/** Reflectively iterate a scala collection into a java list (odb2 has no scala compile dep). */
private static java.util.List<Object> scalaCollectionToList(Object scalaColl) throws IOException {
try {
Object iterator = scalaColl.getClass().getMethod("iterator").invoke(scalaColl);
java.lang.reflect.Method hasNext = iterator.getClass().getMethod("hasNext");
java.lang.reflect.Method next = iterator.getClass().getMethod("next");
hasNext.setAccessible(true);
next.setAccessible(true);
java.util.List<Object> out = new java.util.ArrayList<>();
while ((Boolean) hasNext.invoke(iterator)) out.add(next.invoke(iterator));
return out;
} catch (Exception e) {
throw new IOException("Failed to iterate scala collection " + scalaColl.getClass() + ": " + e.getMessage());
}
}

@SuppressWarnings("unchecked")
private static Map<String, Object> getProperties(DetachedNodeData node) {
if (node instanceof DetachedNodeGeneric) {
Expand Down