From 1cdd6e0b446108a9f4d59eb7d9ed42dc8bf39226 Mon Sep 17 00:00:00 2001 From: Tienson Qin Date: Sun, 11 Oct 2026 02:27:20 -0700 Subject: [PATCH] fix(query): replace Marshal.to_string dedup keys with typed hash tables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Marshal.* externals are not polyfilled by Melange, so every q call crashed when compiled to JS (logseq/logseq CI). Use the existing typed Hashtbl.Make tables in query_api.ml (query_value_hash, query_result_hash, query_result list) instead of marshaled string keys — same dedup semantics, faster, and Melange-safe. --- impl/datascript.ml | 27 ++++++++++++--------------- impl/query_api.ml | 16 ++++++++++++++++ 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/impl/datascript.ml b/impl/datascript.ml index 5e93a53..ec5c02e 100644 --- a/impl/datascript.ml +++ b/impl/datascript.ml @@ -3380,11 +3380,11 @@ module Query = struct |> List.sort_uniq compare | `InColl (attr, values) -> (* attr membership against a bound collection *) - let set = Hashtbl.create (List.length values) in - List.iter (fun value -> Hashtbl.replace set (Marshal.to_string value []) value) values; + let set = Query_api_impl.Query_value_table.create (List.length values) in + List.iter (fun value -> Query_api_impl.Query_value_table.replace set value ()) values; primary_attr_datoms db Aevt attr |> List.filter_map (fun (datom : datom) -> - if Hashtbl.mem set (Marshal.to_string datom.v []) then Some datom.e else None) + if Query_api_impl.Query_value_table.mem set datom.v then Some datom.e else None) |> List.sort_uniq compare | `EntitySet ids -> List.sort_uniq compare ids | `Or _ | `ValueIn _ -> assert false (* handled below *) @@ -3601,7 +3601,7 @@ module Query = struct | Ref id -> Result_entity id | v -> Result_value v in - let seen = Hashtbl.create (List.length entity_ids) in + let seen = Query_api_impl.Query_result_table.create (List.length entity_ids) in entity_ids |> List.filter_map (fun entity_id -> let out = @@ -3609,9 +3609,8 @@ module Query = struct | (d : datom) :: _ -> out_of d.v | [] -> out_of default in - let key = Marshal.to_string out [] in - if Hashtbl.mem seen key then None - else (Hashtbl.replace seen key (); Some out))) + if Query_api_impl.Query_result_table.mem seen out then None + else (Query_api_impl.Query_result_table.replace seen out (); Some out))) | Some (entity_var, `Attr attr) -> simple_attr_entity_ids db ?resolve ?coll entity_var query |> Option.map (fun entity_ids -> @@ -3626,7 +3625,7 @@ module Query = struct | Some bound -> fun (d : datom) -> compare_value d.v bound = 0 | None -> fun _ -> true in - let seen = Hashtbl.create (List.length entity_ids) in + let seen = Query_api_impl.Query_result_table.create (List.length entity_ids) in primary_attr_datoms db Aevt attr |> List.filter_map (fun (d : datom) -> if d.e >= 0 && d.e < Bytes.length wanted @@ -3636,9 +3635,8 @@ module Query = struct | Ref id -> Result_entity id | _ -> Result_value d.v in - let key = Marshal.to_string out [] in - if Hashtbl.mem seen key then None - else (Hashtbl.replace seen key (); Some out) + if Query_api_impl.Query_result_table.mem seen out then None + else (Query_api_impl.Query_result_table.replace seen out (); Some out) else None)) let simple_attr_entity_collection db ?resolve ?coll query = @@ -4179,12 +4177,11 @@ module Query = struct let rows = if List.mem find_var find_var_names then rows else - let seen = Hashtbl.create (List.length rows) in + let seen = Query_api_impl.List_row_table.create (List.length rows) in List.filter (fun row -> - let key = Marshal.to_string row [] in - if Hashtbl.mem seen key then false - else (Hashtbl.replace seen key (); true)) + if Query_api_impl.List_row_table.mem seen row then false + else (Query_api_impl.List_row_table.replace seen row (); true)) rows in Query_relation rows)) diff --git a/impl/query_api.ml b/impl/query_api.ml index 92576eb..afcbdf1 100644 --- a/impl/query_api.ml +++ b/impl/query_api.ml @@ -214,6 +214,22 @@ end) = struct let hash row = List.fold_left (fun acc v -> acc * 31 + query_result_hash v) 0 row end) + module Query_value_table = Hashtbl.Make (struct + type t = value + + let equal left right = compare_query_value left right = 0 + + let hash = query_value_hash + end) + + module Query_result_table = Hashtbl.Make (struct + type t = query_result + + let equal left right = compare_query_result left right = 0 + + let hash = query_result_hash + end) + (* O(n) hash dedup preserving first-occurrence order. [q] results are sets, so row order is unspecified — sorting survivors would only add O(n log n) cost for no observable difference. *)