From a7813c3e9c7aaaa8842b41896c95c7639745ad01 Mon Sep 17 00:00:00 2001 From: Taewoon Kim Date: Sun, 25 Oct 2026 21:55:21 +0900 Subject: [PATCH] Native glue: apply the P-04 and P-01b hunks 99a9dc2 dropped (graph batches via db.batch(), parallel insert_many into a vertex type creates vertices) Commit 99a9dc2 was meant to port humemai/arcadedb-embedded-python#538, and its tests came across, but its NativeExtras.java change is the XML-limits helper (loadWithoutXmlLimits) and nothing else: the two hunks of #538 never reached the native glue, so the native build still failed 5 tests (test_insert_many_parallel and test_insert_columns_parallel: "Cannot create a document of type 'Vtx' because is not a document type"; test_graph_batch_close_refused_in_the_callers_transaction_can_be_retried and test_a_second_graph_batch_is_refused_while_one_is_open: DID NOT RAISE; test_two_concurrent_batches_on_one_hub_lose_no_edge: "Slot rebase not possible" on close). P-04 (data loss): arcadedb_n_gb_create built its GraphBatch with GraphBatch.builder(db), which takes no part in LocalDatabase.batchStarted. It now opens the batch with db.batch(), as the JPype path does, so build() takes the guard and close() gives it back. The Python API is unchanged. P-01b: the async insert_parallel helper called newDocument for every row. It now looks the type up once per call (the rule of humemai #519 / DocumentBatcher): a vertex type gets newVertex, a document type newDocument, an edge type is refused with a message naming it. Needs a rebuilt native library. Checked on a fresh image built from this tree (native/scripts/build-image.sh, engine 26.10.2): test_bulk_insert.py and test_graph_batch.py 87 passed, 4 skipped (all NA-JVM), test_core.py 39 passed, test_concurrency.py 5 passed. Co-Authored-By: Claude Sonnet 5.5 --- .../org/humemai/nativelib/NativeExtras.java | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/native/src/main/java/org/humemai/nativelib/NativeExtras.java b/native/src/main/java/org/humemai/nativelib/NativeExtras.java index 8b86316..aa8879d 100644 --- a/native/src/main/java/org/humemai/nativelib/NativeExtras.java +++ b/native/src/main/java/org/humemai/nativelib/NativeExtras.java @@ -35,6 +35,9 @@ import com.arcadedb.integration.importer.Importer; import com.arcadedb.query.sql.executor.CommandContext; import com.arcadedb.query.sql.executor.ResultSet; +import com.arcadedb.schema.DocumentType; +import com.arcadedb.schema.EdgeType; +import com.arcadedb.schema.VertexType; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.c.function.CEntryPoint; import org.graalvm.nativeimage.c.function.CFunctionPointer; @@ -240,7 +243,9 @@ public static CCharPointer gbCreate(final IsolateThread thread, final long dbh, try { final Database db = ArcadeNative.db(dbh); final Map o = args(options); - GraphBatch.Builder b = GraphBatch.builder(db); + // db.batch(), as the JPype path: it hands the builder the engine's single-batch guard (LocalDatabase.batchStarted, taken by build() and + // released by close()), which GraphBatch.builder(db) bypasses; a second batch while one is open is refused instead of losing edges (P-04) + GraphBatch.Builder b = db.batch(); for (final Map.Entry e : o.entrySet()) { final Object v = e.getValue(); final int iv = intOf(v, 0); @@ -385,6 +390,19 @@ private static Object[] objects(final Object list) { return ((List) list).toArray(); } + /** + * Whether a bulk load into the type creates vertices (true) or documents (false). + * + * @throws IllegalArgumentException for an edge type: an edge cannot be created without its two endpoints + */ + private static boolean isVertexType(final Database db, final String typeName) { + final DocumentType type = db.getSchema().getType(typeName); + if (type instanceof EdgeType) + throw new IllegalArgumentException("Cannot bulk-insert into '" + typeName + + "' because it is an edge type; load the vertices, then create the edges (graph_batch().new_edges() or CREATE EDGE)"); + return type instanceof VertexType; + } + /** * ops: get | set {key,value} | wait {timeout_ms} | kill | create_record {type, kind, props, cb, err} | command / query {lang, text, params, * cb, err} | on_ok {cb} | on_error {cb} | append_samples {type, ts, cols} | insert_parallel {type, rows} | failures {fid}. @@ -539,8 +557,12 @@ public void onError(final Exception e) { }; final String type = (String) a.get("type"); try { - for (final Object row : (List) a.get("rows")) { - final MutableDocument doc = db.newDocument(type); + final List rows = (List) a.get("rows"); + // the type is looked up once per call, as DocumentBatcher does on the JPype path (humemai #519): a vertex type gets vertices, + // a document type documents, an edge type is refused (an edge needs both endpoints); a missing type raises the schema's own error + final boolean vertex = !rows.isEmpty() && isVertexType(db, type); + for (final Object row : rows) { + final MutableDocument doc = vertex ? db.newVertex(type) : db.newDocument(type); for (final Map.Entry e : ((Map) row).entrySet()) doc.set(e.getKey(), e.getValue()); ex.createRecord(doc, null, onError);