diff --git a/common/src/app/common/features.cljc b/common/src/app/common/features.cljc
index 90ed0930a2..c628d8f0ab 100644
--- a/common/src/app/common/features.cljc
+++ b/common/src/app/common/features.cljc
@@ -56,6 +56,7 @@
"text-editor/v2-html-paste"
"text-editor/v2"
"render-wasm/v1"
+ "graph-wasm/v1"
"variants/v1"})
;; A set of features enabled by default
@@ -79,7 +80,8 @@
"text-editor/v2-html-paste"
"text-editor/v2"
"tokens/numeric-input"
- "render-wasm/v1"})
+ "render-wasm/v1"
+ "graph-wasm/v1"})
;; Features that are mainly backend only or there are a proper
;; fallback when frontend reports no support for it
@@ -128,6 +130,7 @@
:feature-text-editor-v2 "text-editor/v2"
:feature-text-editor-v2-html-paste "text-editor/v2-html-paste"
:feature-render-wasm "render-wasm/v1"
+ :feature-graph-wasm "graph-wasm/v1"
:feature-variants "variants/v1"
:feature-token-input "tokens/numeric-input"
nil))
diff --git a/frontend/resources/wasm-playground/graph.html b/frontend/resources/wasm-playground/graph.html
new file mode 100644
index 0000000000..3a71334ee3
--- /dev/null
+++ b/frontend/resources/wasm-playground/graph.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
diff --git a/frontend/shadow-cljs.edn b/frontend/shadow-cljs.edn
index 5dceccf652..26c48f0701 100644
--- a/frontend/shadow-cljs.edn
+++ b/frontend/shadow-cljs.edn
@@ -92,7 +92,7 @@
{:main
{:entries [app.worker]
:web-worker true
- :prepend-js "importScripts('./render.js');"
+ :prepend-js "importScripts('./render.js', './graph-wasm-worker.js');"
:depends-on #{}}}
:js-options
diff --git a/frontend/src/app/graph_wasm.cljs b/frontend/src/app/graph_wasm.cljs
new file mode 100644
index 0000000000..9191970611
--- /dev/null
+++ b/frontend/src/app/graph_wasm.cljs
@@ -0,0 +1,12 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) KALEIDOS INC
+
+(ns app.graph-wasm
+ "A WASM based render API"
+ (:require
+ [app.graph-wasm.api :as wasm.api]))
+
+(def module wasm.api/module)
diff --git a/frontend/src/app/graph_wasm/api.cljs b/frontend/src/app/graph_wasm/api.cljs
new file mode 100644
index 0000000000..9e352a124a
--- /dev/null
+++ b/frontend/src/app/graph_wasm/api.cljs
@@ -0,0 +1,91 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) KALEIDOS INC
+
+(ns app.graph-wasm.api
+ (:require
+ [app.common.data.macros :as dm]
+ [app.common.uuid :as uuid]
+ [app.config :as cf]
+ [app.graph-wasm.wasm :as wasm]
+ [app.render-wasm.helpers :as h]
+ [app.render-wasm.serializers :as sr]
+ [app.util.modules :as mod]
+ [promesa.core :as p]))
+
+(defn hello []
+ (h/call wasm/internal-module "_hello"))
+
+(defn init []
+ (h/call wasm/internal-module "_init"))
+
+(defn use-shape
+ [id]
+ (let [buffer (uuid/get-u32 id)]
+ (println "use-shape" id)
+ (h/call wasm/internal-module "_use_shape"
+ (aget buffer 0)
+ (aget buffer 1)
+ (aget buffer 2)
+ (aget buffer 3))))
+
+(defn set-shape-parent-id
+ [id]
+ (let [buffer (uuid/get-u32 id)]
+ (h/call wasm/internal-module "_set_shape_parent"
+ (aget buffer 0)
+ (aget buffer 1)
+ (aget buffer 2)
+ (aget buffer 3))))
+
+(defn set-shape-type
+ [type]
+ (h/call wasm/internal-module "_set_shape_type" (sr/translate-shape-type type)))
+
+(defn set-shape-selrect
+ [selrect]
+ (h/call wasm/internal-module "_set_shape_selrect"
+ (dm/get-prop selrect :x1)
+ (dm/get-prop selrect :y1)
+ (dm/get-prop selrect :x2)
+ (dm/get-prop selrect :y2)))
+
+(defn set-object
+ [shape]
+ (let [id (dm/get-prop shape :id)
+ type (dm/get-prop shape :type)
+ parent-id (get shape :parent-id)
+ selrect (get shape :selrect)
+ children (get shape :shapes)]
+ (use-shape id)
+ (set-shape-type type)
+ (set-shape-parent-id parent-id)
+ (set-shape-selrect selrect)))
+
+(defn set-objects
+ [objects]
+ (doseq [shape (vals objects)]
+ (set-object shape)))
+
+(defn init-wasm-module
+ [module]
+ (let [default-fn (unchecked-get module "default")
+ href (cf/resolve-href "js/graph-wasm.wasm")]
+ (default-fn #js {:locateFile (constantly href)})))
+
+(defonce module
+ (delay
+ (if (exists? js/dynamicImport)
+ (let [uri (cf/resolve-href "js/graph-wasm.js")]
+ (->> (mod/import uri)
+ (p/mcat init-wasm-module)
+ (p/fmap (fn [default]
+ (set! wasm/internal-module default)
+ true))
+ (p/merr
+ (fn [cause]
+ (js/console.error cause)
+ (p/resolved false)))))
+ (p/resolved false))))
\ No newline at end of file
diff --git a/frontend/src/app/graph_wasm/wasm.cljs b/frontend/src/app/graph_wasm/wasm.cljs
new file mode 100644
index 0000000000..6792d8b7cc
--- /dev/null
+++ b/frontend/src/app/graph_wasm/wasm.cljs
@@ -0,0 +1,9 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) KALEIDOS INC
+
+(ns app.graph-wasm.wasm)
+
+(defonce internal-module #js {})
diff --git a/frontend/src/app/main/data/workspace/componentize.cljs b/frontend/src/app/main/data/workspace/componentize.cljs
new file mode 100644
index 0000000000..4e94297c8e
--- /dev/null
+++ b/frontend/src/app/main/data/workspace/componentize.cljs
@@ -0,0 +1,288 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) KALEIDOS INC
+;;
+;; High level helpers to turn a shape subtree into a component and
+;; replace equivalent subtrees by instances of that component.
+
+(ns app.main.data.workspace.componentize
+ (:require
+ [app.common.data :as d]
+ [app.common.data.macros :as dm]
+ [app.common.files.changes-builder :as pcb]
+ [app.common.files.helpers :as cfh]
+ [app.common.geom.point :as gpt]
+ [app.common.logic.libraries :as cll]
+ [app.common.logic.shapes :as cls]
+ [app.common.types.shape :as cts]
+ [app.common.uuid :as uuid]
+ [app.main.data.changes :as dch]
+ [app.main.data.helpers :as dsh]
+ [app.main.data.workspace.libraries :as dwl]
+ [app.main.data.workspace.selection :as dws]
+ [app.main.data.workspace.shapes :as dwsh]
+ [app.main.data.workspace.undo :as dwu]
+ [beicon.v2.core :as rx]
+ [potok.v2.core :as ptk]))
+
+;; NOTE: We keep this separate from `workspace.libraries` to avoid
+;; introducing more complexity in that already big namespace.
+
+(def ^:private instance-structural-keys
+ "Keys we do NOT want to copy from the original shape when creating a
+ new component instance. These are identity / structural / component
+ metadata keys that must be managed by the component system itself."
+ #{:id
+ :parent-id
+ :frame-id
+ :shapes
+ ;; Component metadata
+ :component-id
+ :component-file
+ :component-root
+ :main-instance
+ :remote-synced
+ :shape-ref
+ :touched})
+
+(def ^:private instance-geometry-keys
+ "Geometry-related keys that we *do* want to override per instance when
+ copying props from an existing subtree to a component instance."
+ #{:x
+ :y
+ :width
+ :height
+ :rotation
+ :flip-x
+ :flip-y
+ :selrect
+ :points
+ :proportion
+ :proportion-lock
+ :transform
+ :transform-inverse})
+
+(defn- instantiate-similar-subtrees
+ "Internal helper. Given an atom `id-ref` that will contain the
+ `component-id`, replace each subtree rooted at the ids in
+ `similar-ids` by an instance of that component.
+
+ The operation is performed in a single undo transaction:
+ - Instantiate the component once per similar id, roughly at the same
+ top-left position as the original root.
+ - Delete the original subtrees.
+ - Select the main instance plus all the new instances."
+ [id-ref root-id similar-ids]
+ (ptk/reify ::instantiate-similar-subtrees
+ ptk/WatchEvent
+ (watch [it state _]
+ (let [component-id @id-ref
+ similar-ids (vec (or similar-ids []))]
+ (if (or (uuid/zero? component-id)
+ (empty? similar-ids))
+ (rx/empty)
+ (let [file-id (:current-file-id state)
+ page (dsh/lookup-page state)
+ page-id (:id page)
+ objects (:objects page)
+ libraries (dsh/lookup-libraries state)
+ fdata (dsh/lookup-file-data state file-id)
+
+ ;; Reference subtree: shapes used to build the component.
+ ;; We'll compute per-shape deltas against this subtree so
+ ;; that we only override attributes that actually differ.
+ ref-subtree-ids (cfh/get-children-ids objects root-id)
+ ref-all-ids (into [root-id] ref-subtree-ids)
+
+ undo-id (js/Symbol)
+
+ ;; 1) Instantiate component at each similar root position,
+ ;; preserving per-instance overrides (geometry, style, etc.)
+ [changes new-root-ids]
+ (reduce
+ (fn [[changes acc] sid]
+ (if-let [shape (get objects sid)]
+ (let [position (gpt/point (:x shape) (:y shape))
+ ;; Remember original parent and index so we can keep
+ ;; the same ordering among the parent's children.
+ orig-root (get objects sid)
+ orig-parent-id (:parent-id orig-root)
+ orig-index (when orig-parent-id
+ (cfh/get-position-on-parent objects sid))
+ ;; Instantiate a new component instance at the same position
+ [new-shape changes']
+ (cll/generate-instantiate-component
+ (or changes
+ (-> (pcb/empty-changes it page-id)
+ (pcb/with-objects objects)))
+ objects
+ file-id
+ component-id
+ position
+ page
+ libraries)
+ ;; Build a structural mapping between the original subtree
+ ;; (rooted at `sid`) and the new instance subtree.
+ ;; NOTE 1: instantiating a component can introduce an extra
+ ;; wrapper frame, so we try to align the original root
+ ;; with the "equivalent" root inside the instance.
+ ;; NOTE 2: by default the instance may be created *inside*
+ ;; the original shape (because of layout / hit-testing).
+ ;; We explicitly move the new instance to the same parent
+ ;; and index as the original root, so that later deletes of
+ ;; the original subtree don't remove the new instances and
+ ;; the ordering among siblings is preserved.
+ changes' (cond-> changes'
+ (some? orig-parent-id)
+ (pcb/change-parent orig-parent-id [new-shape] orig-index
+ {:allow-altering-copies true
+ :ignore-touched true}))
+ objects' (pcb/get-objects changes')
+ orig-root (get objects sid)
+ new-root new-shape
+ orig-type (:type orig-root)
+ new-type (:type new-root)
+ ;; Full original subtree (root + descendants)
+ orig-subtree-ids (cfh/get-children-ids objects sid)
+ orig-all-ids (into [sid] orig-subtree-ids)
+ ;; Try to find an inner instance root matching the original type
+ ;; when the outer instance root type differs (e.g. rect -> frame+rect).
+ direct-new-children (cfh/get-children-ids objects' (:id new-root))
+ candidate-instance-root
+ (when (and orig-type (not= orig-type new-type))
+ (let [cands (->> direct-new-children
+ (filter (fn [nid]
+ (when-let [s (get objects' nid)]
+ (= (:type s) orig-type)))))]
+ (when (= 1 (count cands))
+ (first cands))))
+ instance-root-id (or candidate-instance-root (:id new-root))
+ instance-root (get objects' instance-root-id)
+ new-subtree-ids (cfh/get-children-ids objects' instance-root-id)
+ new-all-ids (into [instance-root-id] new-subtree-ids)
+ id-pairs (map vector orig-all-ids new-all-ids)
+ changes''
+ ;; Compute per-shape deltas against the reference
+ ;; subtree (root-id) and apply only the differences
+ ;; to the new instance subtree, so we don't blindly
+ ;; overwrite attributes that are the same.
+ (reduce
+ (fn [ch [idx orig-id new-id]]
+ (let [ref-id (nth ref-all-ids idx nil)
+ ref-shape (get objects ref-id)
+ orig-shape (get objects orig-id)]
+ (if (and ref-shape orig-shape)
+ (let [;; Style / layout / text props (see `extract-props`)
+ ref-style (cts/extract-props ref-shape)
+ orig-style (cts/extract-props orig-shape)
+ style-delta (reduce (fn [m k]
+ (let [rv (get ref-style k ::none)
+ ov (get orig-style k ::none)]
+ (if (= rv ov)
+ m
+ (assoc m k ov))))
+ {}
+ (keys orig-style))
+
+ ;; Geometry props
+ ref-geom (select-keys ref-shape instance-geometry-keys)
+ orig-geom (select-keys orig-shape instance-geometry-keys)
+ geom-delta (reduce (fn [m k]
+ (let [rv (get ref-geom k ::none)
+ ov (get orig-geom k ::none)]
+ (if (= rv ov)
+ m
+ (assoc m k ov))))
+ {}
+ (keys orig-geom))
+
+ ;; Text content: if the subtree reference and the
+ ;; original differ in `:content`, treat the whole
+ ;; content tree as an override for this instance.
+ content-delta? (not= (:content ref-shape) (:content orig-shape))]
+ (-> ch
+ ;; First patch style/text/layout props using the
+ ;; canonical helpers so we don't touch structural ids.
+ (cond-> (seq style-delta)
+ (pcb/update-shapes
+ [new-id]
+ (fn [s objs] (cts/patch-props s style-delta objs))
+ {:with-objects? true}))
+ ;; Then patch geometry directly on the instance.
+ (cond-> (seq geom-delta)
+ (pcb/update-shapes
+ [new-id]
+ (d/patch-object geom-delta)))
+ ;; Finally, if text content differs between the
+ ;; reference subtree and the similar subtree,
+ ;; override the instance content with the original.
+ (cond-> content-delta?
+ (pcb/update-shapes
+ [new-id]
+ #(assoc % :content (:content orig-shape))))))
+ ch)))
+ changes'
+ (map-indexed (fn [idx [orig-id new-id]]
+ [idx orig-id new-id])
+ id-pairs))]
+ [changes'' (conj acc (:id new-shape))])
+ ;; If the shape does not exist we just skip it
+ [changes acc]))
+ [nil []]
+ similar-ids)
+
+ changes (or changes
+ (-> (pcb/empty-changes it page-id)
+ (pcb/with-objects objects)))
+
+ ;; 2) Delete original similar subtrees
+ ;; NOTE: `d/ordered-set` with a single arg treats it as a single
+ ;; element, so we must use `into` when we already have a collection.
+ ids-to-delete (into (d/ordered-set) similar-ids)
+ [all-parents changes]
+ (cls/generate-delete-shapes
+ changes
+ fdata
+ page
+ objects
+ ids-to-delete
+ {:allow-altering-copies true})
+
+ ;; 3) Select main instance + new instances
+ ;; Root id is kept as-is; add all new roots.
+ sel-ids (into (d/ordered-set) (cons root-id new-root-ids))]
+
+ (rx/of
+ (dwu/start-undo-transaction undo-id)
+ (dch/commit-changes changes)
+ (ptk/data-event :layout/update {:ids all-parents})
+ (dwu/commit-undo-transaction undo-id))))))))
+
+(defn componentize-similar-subtrees
+ "Turn the subtree rooted at `root-id` into a component, then replace
+ the subtrees rooted at `similar-ids` with instances of that component.
+
+ This is implemented in two phases:
+ 1) Use the existing `dwl/add-component` flow to create a component
+ from `root-id` (and obtain its `component-id`).
+ 2) Using the new `component-id`, instantiate the component once per
+ entry in `similar-ids` and delete the old subtrees."
+ [root-id similar-ids]
+ (dm/assert!
+ "expected valid uuid for `root-id`"
+ (uuid? root-id))
+
+ (let [similar-ids (vec (or similar-ids []))]
+ (ptk/reify ::componentize-similar-subtrees
+ ptk/WatchEvent
+ (watch [_ _ _]
+ (let [id-ref (atom uuid/zero)]
+ (rx/concat
+ ;; 1) Create component using the existing pipeline
+ (rx/of (dwl/add-component id-ref (d/ordered-set root-id)))
+ ;; 2) Replace similar subtrees by instances of the new component
+ (rx/of (instantiate-similar-subtrees id-ref root-id similar-ids))))))))
+
+
diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs
index 7d08054fb4..52da3b5806 100644
--- a/frontend/src/app/main/ui/workspace/viewport.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport.cljs
@@ -13,11 +13,16 @@
[app.common.geom.shapes :as gsh]
[app.common.types.color :as clr]
[app.common.types.component :as ctk]
+ [app.common.types.container :as ctn]
[app.common.types.path :as path]
[app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctt]
[app.common.types.shape.layout :as ctl]
+ [app.config :as cf]
+ [app.graph-wasm.api :as graph-wasm.api]
+ [app.main.data.workspace.componentize :as dwc]
[app.main.data.workspace.modifiers :as dwm]
+ [app.main.data.workspace.selection :as dws]
[app.main.data.workspace.variants :as dwv]
[app.main.features :as features]
[app.main.refs :as refs]
@@ -57,8 +62,11 @@
[app.main.ui.workspace.viewport.utils :as utils]
[app.main.ui.workspace.viewport.viewport-ref :refer [create-viewport-ref]]
[app.main.ui.workspace.viewport.widgets :as widgets]
+ [app.main.worker :as worker]
[app.util.debug :as dbg]
+ [app.util.modules :as mod]
[beicon.v2.core :as rx]
+ [promesa.core :as p]
[rumext.v2 :as mf]))
;; --- Viewport
@@ -134,6 +142,7 @@
mod? (mf/use-state false)
space? (mf/use-state false)
z? (mf/use-state false)
+ g? (mf/use-state false)
cursor (mf/use-state #(utils/get-cursor :pointer-inner))
hover-ids (mf/use-state nil)
hover (mf/use-state nil)
@@ -302,12 +311,79 @@
(mf/use-fn
(mf/deps first-shape)
#(st/emit!
- (dwv/add-new-variant (:id first-shape))))]
+ (dwv/add-new-variant (:id first-shape))))
+
+ graph-wasm-enabled? (features/use-feature "graph-wasm/v1")]
+
+ (mf/with-effect [page-id]
+ (when graph-wasm-enabled?
+ ;; Initialize graph-wasm in the worker to avoid blocking main thread
+ (let [subscription
+ (->> (worker/ask! {:cmd :graph-wasm/init})
+ (rx/filter #(= (:status %) :ok))
+ (rx/take 1)
+ (rx/merge-map (fn [_]
+ (worker/ask! {:cmd :graph-wasm/set-objects
+ :objects base-objects}))))]
+
+ (rx/subscribe subscription
+ (fn [result]
+ (when (= (:status result) :ok)
+ (js/console.debug "Graph WASM initialized in worker"
+ (select-keys result [:processed]))))
+ (fn [error]
+ (js/console.error "Error initializing graph-wasm in worker:" error))
+ (fn []
+ (js/console.debug "Graph WASM worker operations completed"))))))
+
+ (mf/with-effect [selected @g?]
+ (when graph-wasm-enabled?
+ ;; Search for similar shapes when selection changes or when
+ ;; the user presses the \"c\" key while having a single
+ ;; selection.
+ (when (and @g?
+ (some? selected)
+ (= (count selected) 1))
+ (let [selected-id (first selected)
+ selected-shape (get base-objects selected-id)
+ ;; Skip shapes that already belong to a component
+ non-component? (and (some? selected-shape)
+ (not (ctn/in-any-component? base-objects selected-shape)))]
+
+ (println selected-shape)
+ (println (ctn/in-any-component? base-objects selected-shape))
+
+ (when non-component?
+ (let [subscription
+ (worker/ask! {:cmd :graph-wasm/search-similar-shapes
+ :shape-id selected-id})]
+
+ (rx/subscribe subscription
+ (fn [result]
+ (when (= (:status result) :ok)
+ (let [raw-similar-shapes (:similar-shapes result)
+ ;; Filter out shapes that already belong to some component
+ ;; (main instance, instance head or inside a component copy).
+ similar-shapes (->> raw-similar-shapes
+ (remove (fn [sid]
+ (when-let [s (get base-objects sid)]
+ (ctn/in-any-component? base-objects s))))
+ (into []))]
+ (when (d/not-empty? similar-shapes)
+ ;; Transform the selected subtree into a component and
+ ;; replace similar subtrees by instances of that component.
+ (st/emit! (dwc/componentize-similar-subtrees
+ selected-id
+ similar-shapes))))))
+ (fn [error]
+ (js/console.error "Error searching similar shapes:" error))
+ (fn []
+ (js/console.debug "Similar shapes search completed")))))))))
(hooks/setup-dom-events zoom disable-paste-ref in-viewport-ref read-only? drawing-tool path-drawing?)
(hooks/setup-viewport-size vport viewport-ref)
(hooks/setup-cursor cursor alt? mod? space? panning drawing-tool path-drawing? path-editing? z? read-only?)
- (hooks/setup-keyboard alt? mod? space? z? shift?)
+ (hooks/setup-keyboard alt? mod? space? z? shift? g?)
(hooks/setup-hover-shapes page-id move-stream base-objects transform selected mod? hover measure-hover
hover-ids hover-top-frame-id @hover-disabled? focus zoom show-measures?)
(hooks/setup-viewport-modifiers modifiers base-objects)
diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs
index 922e18057d..af8f4ad692 100644
--- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs
@@ -124,7 +124,7 @@
(reset! cursor new-cursor))))))
(defn setup-keyboard
- [alt* mod* space* z* shift*]
+ [alt* mod* space* z* shift* g*]
(let [kbd-zoom-s
(mf/with-memo []
(->> ms/keyboard
@@ -151,12 +151,22 @@
(rx/filter kbd/z?)
(rx/filter (complement kbd/editing-event?))
(rx/map kbd/key-down-event?)
- (rx/pipe (rxo/distinct-contiguous))))]
+ (rx/pipe (rxo/distinct-contiguous))))
+
+ kbd-g-s
+ (mf/with-memo []
+ (let [c-pred (kbd/is-key-ignore-case? "g")]
+ (->> ms/keyboard
+ (rx/filter c-pred)
+ (rx/filter (complement kbd/editing-event?))
+ (rx/map kbd/key-down-event?)
+ (rx/pipe (rxo/distinct-contiguous)))))]
(hooks/use-stream ms/keyboard-alt (partial reset! alt*))
(hooks/use-stream ms/keyboard-space (partial reset! space*))
(hooks/use-stream kbd-z-s (partial reset! z*))
(hooks/use-stream kbd-shift-s (partial reset! shift*))
+ (hooks/use-stream kbd-g-s (partial reset! g*))
(hooks/use-stream ms/keyboard-mod
(fn [value]
(reset! mod* value)
diff --git a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs
index a667d3abc5..2d83f6c3d7 100644
--- a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs
@@ -122,6 +122,7 @@
mod? (mf/use-state false)
space? (mf/use-state false)
z? (mf/use-state false)
+ c? (mf/use-state false)
cursor (mf/use-state (utils/get-cursor :pointer-inner))
hover-ids (mf/use-state nil)
hover (mf/use-state nil)
@@ -360,7 +361,7 @@
(hooks/setup-dom-events zoom disable-paste-ref in-viewport-ref read-only? drawing-tool path-drawing?)
(hooks/setup-viewport-size vport viewport-ref)
(hooks/setup-cursor cursor alt? mod? space? panning drawing-tool path-drawing? path-editing? z? read-only?)
- (hooks/setup-keyboard alt? mod? space? z? shift?)
+ (hooks/setup-keyboard alt? mod? space? z? shift? c?)
(hooks/setup-hover-shapes page-id move-stream base-objects transform selected mod? hover measure-hover
hover-ids hover-top-frame-id @hover-disabled? focus zoom show-measures?)
(hooks/setup-shortcuts path-editing? path-drawing? text-editing? grid-editing?)
diff --git a/frontend/src/app/worker.cljs b/frontend/src/app/worker.cljs
index e6ca3fb1d2..56e33d7d12 100644
--- a/frontend/src/app/worker.cljs
+++ b/frontend/src/app/worker.cljs
@@ -11,6 +11,7 @@
[app.common.schema :as sm]
[app.common.types.objects-map]
[app.util.object :as obj]
+ [app.worker.graph-wasm]
[app.worker.impl :as impl]
[app.worker.import]
[app.worker.index]
diff --git a/frontend/src/app/worker/graph_wasm.cljs b/frontend/src/app/worker/graph_wasm.cljs
new file mode 100644
index 0000000000..07340b8f41
--- /dev/null
+++ b/frontend/src/app/worker/graph_wasm.cljs
@@ -0,0 +1,181 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) KALEIDOS INC
+
+(ns app.worker.graph-wasm
+ "Graph WASM operations within the worker."
+ (:require
+ [app.common.data.macros :as dm]
+ [app.common.logging :as log]
+ [app.common.uuid :as uuid]
+ [app.config :as cf]
+ [app.graph-wasm.wasm :as wasm]
+ [app.render-wasm.helpers :as h]
+ [app.render-wasm.serializers :as sr]
+ [app.worker.impl :as impl]
+ [beicon.v2.core :as rx]
+ [promesa.core :as p]))
+
+(log/set-level! :info)
+
+(defn- use-shape
+ [module id]
+ (let [buffer (uuid/get-u32 id)]
+ (h/call module "_use_shape"
+ (aget buffer 0)
+ (aget buffer 1)
+ (aget buffer 2)
+ (aget buffer 3))))
+
+(defn- set-shape-parent-id
+ [module id]
+ (let [buffer (uuid/get-u32 id)]
+ (h/call module "_set_shape_parent"
+ (aget buffer 0)
+ (aget buffer 1)
+ (aget buffer 2)
+ (aget buffer 3))))
+
+(defn- set-shape-type
+ [module type]
+ (h/call module "_set_shape_type" (sr/translate-shape-type type)))
+
+(defn- set-shape-selrect
+ [module selrect]
+ (h/call module "_set_shape_selrect"
+ (dm/get-prop selrect :x1)
+ (dm/get-prop selrect :y1)
+ (dm/get-prop selrect :x2)
+ (dm/get-prop selrect :y2)))
+
+(defn- set-object
+ [module shape]
+ (let [id (dm/get-prop shape :id)
+ type (dm/get-prop shape :type)
+ parent-id (get shape :parent-id)
+ selrect (get shape :selrect)]
+ (use-shape module id)
+ (set-shape-type module type)
+ (set-shape-parent-id module parent-id)
+ (set-shape-selrect module selrect)))
+
+(defonce ^:private graph-wasm-module
+ (delay
+ (let [module (unchecked-get js/globalThis "GraphWasmModule")
+ init-fn (unchecked-get module "default")
+ href (cf/resolve-href "js/graph-wasm.wasm")]
+ (->> (init-fn #js {:locateFile (constantly href)})
+ (p/fnly (fn [module cause]
+ (if cause
+ (js/console.error cause)
+ (set! wasm/internal-module module))))))))
+
+(defmethod impl/handler :graph-wasm/init
+ [message transfer]
+ (rx/create
+ (fn [subs]
+ (-> @graph-wasm-module
+ (p/then (fn [module]
+ (if module
+ (try
+ (h/call module "_init")
+ (rx/push! subs {:status :ok})
+ (rx/end! subs)
+ (catch :default cause
+ (log/error :hint "Error in graph-wasm/init" :cause cause)
+ (rx/error! subs cause)
+ (rx/end! subs)))
+ (do
+ (log/warn :hint "Graph WASM module not available")
+ (rx/push! subs {:status :error :message "Module not available"})
+ (rx/end! subs)))))
+ (p/catch (fn [cause]
+ (log/error :hint "Error loading graph-wasm module" :cause cause)
+ (rx/error! subs cause)
+ (rx/end! subs))))
+ nil)))
+
+(defmethod impl/handler :graph-wasm/set-objects
+ [message transfer]
+ (let [objects (:objects message)]
+ (rx/create
+ (fn [subs]
+ (-> @graph-wasm-module
+ (p/then (fn [module]
+ (if module
+ (try
+ (doseq [shape (vals objects)]
+ (set-object module shape))
+ (h/call module "_generate_db")
+ (rx/push! subs {:status :ok :processed (count objects)})
+ (rx/end! subs)
+ (catch :default cause
+ (log/error :hint "Error in graph-wasm/set-objects" :cause cause)
+ (rx/error! subs cause)
+ (rx/end! subs)))
+ (do
+ (log/warn :hint "Graph WASM module not available")
+ (rx/push! subs {:status :error :message "Module not available"})
+ (rx/end! subs)))))
+ (p/catch (fn [cause]
+ (log/error :hint "Error loading graph-wasm module" :cause cause)
+ (rx/error! subs cause)
+ (rx/end! subs))))
+ nil))))
+
+(defmethod impl/handler :graph-wasm/search-similar-shapes
+ [message transfer]
+ (let [shape-id (:shape-id message)]
+ (rx/create
+ (fn [subs]
+ (-> @graph-wasm-module
+ (p/then (fn [module]
+ (if module
+ (try
+ (let [buffer (uuid/get-u32 shape-id)
+ ptr-raw (h/call module "_search_similar_shapes"
+ (aget buffer 0)
+ (aget buffer 1)
+ (aget buffer 2)
+ (aget buffer 3))
+ ;; Convert pointer to unsigned 32-bit (handle negative numbers from WASM)
+ ;; Use unsigned right shift to convert signed to unsigned 32-bit
+ ptr (unsigned-bit-shift-right ptr-raw 0)
+ heapu8 (unchecked-get module "HEAPU8")
+
+ ;; Read count (first 4 bytes, little-endian u32)
+ count (bit-or (aget heapu8 ptr)
+ (bit-shift-left (aget heapu8 (+ ptr 1)) 8)
+ (bit-shift-left (aget heapu8 (+ ptr 2)) 16)
+ (bit-shift-left (aget heapu8 (+ ptr 3)) 24))
+ ;; Read UUIDs (16 bytes each, starting at offset 4)
+ similar-shapes (loop [offset (+ ptr 4)
+ remaining count
+ result []]
+ (if (zero? remaining)
+ result
+ (let [uuid-bytes (.slice heapu8 offset (+ offset 16))]
+ (recur (+ offset 16)
+ (dec remaining)
+ (conj result (uuid/from-bytes uuid-bytes))))))]
+
+ ;; Free the buffer
+ (h/call module "_free_similar_shapes_buffer")
+
+ (rx/push! subs {:status :ok :similar-shapes similar-shapes})
+ (rx/end! subs))
+ (catch :default cause
+ (log/error :hint "Error in graph-wasm/search-similar-shapes" :cause cause)
+ (rx/error! subs cause)
+ (rx/end! subs)))
+ (do
+ (log/warn :hint "Graph WASM module not available")
+ (rx/push! subs {:status :error :message "Module not available"})
+ (rx/end! subs)))))
+ (p/catch (fn [cause]
+ (log/error :hint "Error loading graph-wasm module" :cause cause)
+ (rx/error! subs cause)
+ (rx/end! subs))))
+ nil))))
diff --git a/frontend/test/frontend_tests/svg_filters_test.cljs b/frontend/test/frontend_tests/svg_filters_test.cljs
index d469183389..8fccd29bdb 100644
--- a/frontend/test/frontend_tests/svg_filters_test.cljs
+++ b/frontend/test/frontend_tests/svg_filters_test.cljs
@@ -47,3 +47,6 @@
result (svg-filters/apply-svg-filters shape)]
(is (= shape result))))
+
+
+
diff --git a/graph-wasm/.cargo/config.toml b/graph-wasm/.cargo/config.toml
new file mode 100644
index 0000000000..46eec127dc
--- /dev/null
+++ b/graph-wasm/.cargo/config.toml
@@ -0,0 +1,6 @@
+[target.wasm32-unknown-emscripten]
+# Note: Not using atomics to avoid recompiling std library
+# We're running without pthreads, so lbug needs to work single-threaded
+rustflags = ["-C", "link-arg=-fexceptions"]
+# Linker is configured via environment variable CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_LINKER in _build_env
+
diff --git a/graph-wasm/.gitignore b/graph-wasm/.gitignore
new file mode 100644
index 0000000000..391ed4d660
--- /dev/null
+++ b/graph-wasm/.gitignore
@@ -0,0 +1,5 @@
+target/
+debug/
+
+**/*.rs.bk
+
diff --git a/graph-wasm/Cargo.lock b/graph-wasm/Cargo.lock
new file mode 100644
index 0000000000..3526a993e2
--- /dev/null
+++ b/graph-wasm/Cargo.lock
@@ -0,0 +1,486 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "anstyle"
+version = "1.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
+
+[[package]]
+name = "arrayvec"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+
+[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
+name = "bumpalo"
+version = "3.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
+
+[[package]]
+name = "cc"
+version = "1.2.49"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215"
+dependencies = [
+ "find-msvc-tools",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "clap"
+version = "4.5.53"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
+dependencies = [
+ "clap_builder",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.53"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
+dependencies = [
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
+
+[[package]]
+name = "cmake"
+version = "0.1.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b042e5d8a74ae91bb0961acd039822472ec99f8ab0948cbf6d1369588f8be586"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "codespan-reporting"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
+dependencies = [
+ "termcolor",
+ "unicode-width",
+]
+
+[[package]]
+name = "cxx"
+version = "1.0.138"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3956d60afa98653c5a57f60d7056edd513bfe0307ef6fb06f6167400c3884459"
+dependencies = [
+ "cc",
+ "cxxbridge-cmd",
+ "cxxbridge-flags",
+ "cxxbridge-macro",
+ "foldhash",
+ "link-cplusplus",
+]
+
+[[package]]
+name = "cxx-build"
+version = "1.0.138"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a4b7522f539fe056f1d6fc8577d8ab731451f6f33a89b1e5912e22b76c553e7"
+dependencies = [
+ "cc",
+ "codespan-reporting",
+ "proc-macro2",
+ "quote",
+ "scratch",
+ "syn",
+]
+
+[[package]]
+name = "cxxbridge-cmd"
+version = "1.0.138"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f01e92ab4ce9fd4d16e3bb11b158d98cbdcca803c1417aa43130a6526fbf208"
+dependencies = [
+ "clap",
+ "codespan-reporting",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "cxxbridge-flags"
+version = "1.0.138"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c41cbfab344869e70998b388923f7d1266588f56c8ca284abf259b1c1ffc695"
+
+[[package]]
+name = "cxxbridge-macro"
+version = "1.0.138"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88d82a2f759f0ad3eae43b96604efd42b1d4729a35a6f2dc7bdb797ae25d9284"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rustversion",
+ "syn",
+]
+
+[[package]]
+name = "deranged"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
+
+[[package]]
+name = "foldhash"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+
+[[package]]
+name = "getrandom"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasip2",
+]
+
+[[package]]
+name = "graph"
+version = "0.1.0"
+dependencies = [
+ "lbug",
+ "uuid",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lbug"
+version = "0.12.2"
+dependencies = [
+ "cmake",
+ "cxx",
+ "cxx-build",
+ "rust_decimal",
+ "rustversion",
+ "time",
+ "uuid",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.178"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
+
+[[package]]
+name = "link-cplusplus"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f78c730aaa7d0b9336a299029ea49f9ee53b0ed06e9202e8cb7db9bae7b8c82"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "num-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.103"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "rust_decimal"
+version = "1.39.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35affe401787a9bd846712274d97654355d21b2a2c092a3139aabe31e9022282"
+dependencies = [
+ "arrayvec",
+ "num-traits",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+
+[[package]]
+name = "scratch"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d68f2ec51b097e4c1a75b681a8bec621909b5e91f15bb7b840c4f2f7b01148b2"
+
+[[package]]
+name = "serde"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "syn"
+version = "2.0.111"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "time"
+version = "0.3.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
+dependencies = [
+ "deranged",
+ "num-conv",
+ "powerfmt",
+ "serde",
+ "time-core",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
+
+[[package]]
+name = "uuid"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
+dependencies = [
+ "getrandom",
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "wasip2"
+version = "1.0.1+wasi-0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "winapi-util"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "wit-bindgen"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
diff --git a/graph-wasm/Cargo.toml b/graph-wasm/Cargo.toml
new file mode 100644
index 0000000000..575781c60c
--- /dev/null
+++ b/graph-wasm/Cargo.toml
@@ -0,0 +1,31 @@
+[package]
+name = "graph"
+version = "0.1.0"
+edition = "2021"
+repository = "https://github.com/penpot/penpot"
+license-file = "../LICENSE"
+description = "Wasm-based graph module for Penpot"
+
+build = "build.rs"
+
+[[bin]]
+name = "graph_wasm"
+path = "src/main.rs"
+
+[profile.release]
+opt-level = "s"
+# Note: We need panic=unwind because core requires it
+# We'll compile std from source with atomics support instead
+
+[profile.dev]
+# Note: We need panic=unwind because core requires it
+# We'll compile std from source with atomics support instead
+
+[dependencies]
+lbug = "0.12.2"
+uuid = { version = "1.11.0", features = ["v4", "js"] }
+
+# Patch lbug to use local version with wasm32-unknown-emscripten support
+[patch.crates-io]
+lbug = { path = "./lbug-0.12.2" }
+
diff --git a/graph-wasm/README_WASM.md b/graph-wasm/README_WASM.md
new file mode 100644
index 0000000000..f5912db5c4
--- /dev/null
+++ b/graph-wasm/README_WASM.md
@@ -0,0 +1,197 @@
+# lbug WASM Support
+
+This document describes the modifications made to the `lbug` crate to enable compilation for the `wasm32-unknown-emscripten` target, and the build requirements for using it in a WASM context.
+
+## Overview
+
+The `lbug` crate is a Rust wrapper around a C++ graph database library. To compile it for WebAssembly using Emscripten, several modifications were necessary to handle:
+
+1. C++ exception handling in WASM
+2. Conditional compilation for WASM-specific code paths
+3. Proper linking of static libraries for Emscripten
+4. CMake configuration for single-threaded mode
+
+## Changes Made to lbug
+
+### 1. `build.rs` Modifications
+
+The build script (`build.rs`) was modified to detect and handle the `wasm32-unknown-emscripten` target:
+
+#### WASM Detection
+```rust
+fn is_wasm_emscripten() -> bool {
+ env::var("TARGET")
+ .map(|t| t == "wasm32-unknown-emscripten")
+ .unwrap_or(false)
+}
+```
+
+#### CMake Configuration (`build_bundled_cmake()`)
+- **Single-threaded mode**: Sets `SINGLE_THREADED=TRUE` for WASM builds (required by Emscripten)
+- The Emscripten toolchain is automatically detected when `CC`/`CXX` point to `emcc`/`em++`
+
+#### FFI Build Configuration (`build_ffi()`)
+- **C++20 standard**: Uses `-std=c++20` flag for WASM
+- **Exception support**: Enables `-fexceptions` flag (exceptions must be enabled at compile time)
+- Note: `-sDISABLE_EXCEPTION_CATCHING=0` is a linker flag and should be set via `EMCC_CFLAGS`
+
+#### Library Linking (`link_libraries()`)
+- **Explicit dependency linking**: For WASM, all static dependencies are explicitly linked:
+ - `utf8proc`, `antlr4_cypher`, `antlr4_runtime`, `re2`, `fastpfor`
+ - `parquet`, `thrift`, `snappy`, `zstd`, `miniz`
+ - `mbedtls`, `brotlidec`, `brotlicommon`, `lz4`
+ - `roaring_bitmap`, `simsimd`
+- **Linking order**: Libraries are linked after FFI compilation for WASM (different from native builds)
+
+### 2. `src/error.rs` Modifications
+
+The error handling code was modified to conditionally compile C++ exception support:
+
+#### Conditional C++ Exception Variant
+The `Error::CxxException` variant and related implementations are conditionally compiled:
+
+```rust
+#[cfg(not(target_arch = "wasm32"))]
+pub enum Error {
+ // ... other variants ...
+ CxxException(cxx::Exception),
+ // ...
+}
+```
+
+#### Exception Mapping for WASM
+In WASM builds, `cxx::Exception` is mapped to `Error::FailedQuery`:
+
+```rust
+impl From for Error {
+ fn from(item: cxx::Exception) -> Self {
+ #[cfg(not(target_arch = "wasm32"))]
+ {
+ Error::CxxException(item)
+ }
+ #[cfg(target_arch = "wasm32")]
+ {
+ // In wasm, CxxException is not available, map to a generic error
+ Error::FailedQuery(item.to_string())
+ }
+ }
+}
+```
+
+**Note**: This change does not affect the rest of `lbug` due to `#[cfg]` guards, ensuring native builds remain unchanged.
+
+## Build Requirements
+
+### 1. Using the Modified lbug Crate
+
+To use the modified `lbug` crate in your project, add a `[patch.crates-io]` section to your `Cargo.toml`:
+
+```toml
+[dependencies]
+lbug = "0.12.2"
+
+# Patch lbug to use local version with wasm32-unknown-emscripten support
+[patch.crates-io]
+lbug = { path = "./lbug-0.12.2" }
+```
+
+### 2. Emscripten Environment Setup
+
+The build requires Emscripten to be properly configured. The following environment variables should be set:
+
+#### Memory Configuration
+```bash
+export EM_INITIAL_HEAP=$((256 * 1024 * 1024)) # 256 MB initial heap
+export EM_MAXIMUM_MEMORY=$((4 * 1024 * 1024 * 1024)) # 4 GB maximum
+export EM_MEMORY_GROWTH_GEOMETRIC_STEP=0.8
+export EM_MALLOC=dlmalloc
+```
+
+#### Compiler/Linker Configuration
+```bash
+# Prevent cc-rs from adding default flags that conflict with Emscripten
+export CRATE_CC_NO_DEFAULTS=1
+
+# Emscripten compiler flags
+export EMCC_CFLAGS="--no-entry \
+ -sASSERTIONS=1 \
+ -sALLOW_TABLE_GROWTH=1 \
+ -sALLOW_MEMORY_GROWTH=1 \
+ -sINITIAL_HEAP=$EM_INITIAL_HEAP \
+ -sMEMORY_GROWTH_GEOMETRIC_STEP=$EM_MEMORY_GROWTH_GEOMETRIC_STEP \
+ -sMAXIMUM_MEMORY=$EM_MAXIMUM_MEMORY \
+ -sERROR_ON_UNDEFINED_SYMBOLS=0 \
+ -sDISABLE_EXCEPTION_CATCHING=0 \
+ -sEXPORT_NAME=createGraphModule \
+ -sEXPORTED_RUNTIME_METHODS=stringToUTF8,HEAPU8 \
+ -sENVIRONMENT=web \
+ -sMODULARIZE=1 \
+ -sEXPORT_ES6=1"
+```
+
+#### Function Exports
+
+To control which functions are exported (avoiding issues with `$` symbols in auto-generated exports), use `RUSTFLAGS`:
+
+```bash
+export RUSTFLAGS="-C link-arg=-sEXPORTED_FUNCTIONS=@${SCRIPT_DIR}/exports.txt -C link-arg=-sEXPORT_ALL=0"
+```
+
+Where `exports.txt` contains the list of functions to export (one per line, with `_` prefix):
+```
+_hello
+_generate_db
+_init
+_search_similar_shapes
+# ... etc
+```
+
+### 3. Build Process
+
+1. **Source Emscripten environment**:
+ ```bash
+ source /opt/emsdk/emsdk_env.sh
+ ```
+
+2. **Set build environment**:
+ ```bash
+ source ./_build_env
+ ```
+
+3. **Build**:
+ ```bash
+ cargo build --target=wasm32-unknown-emscripten
+ ```
+
+## Key Differences from Native Builds
+
+1. **Single-threaded**: WASM builds use `SINGLE_THREADED=TRUE` in CMake
+2. **Exception handling**: C++ exceptions are enabled at compile time (`-fexceptions`) and runtime (`-sDISABLE_EXCEPTION_CATCHING=0`)
+3. **Linking order**: Libraries are linked after FFI compilation for WASM
+4. **Error handling**: C++ exceptions are mapped to `FailedQuery` errors in WASM
+5. **Function exports**: Manual control of exported functions via `EXPORTED_FUNCTIONS` file
+
+## Troubleshooting
+
+### Missing Symbols
+If you encounter "missing function" errors at runtime, ensure:
+- All required static libraries are listed in `link_libraries()` for WASM
+- Libraries are linked in the correct order (after FFI compilation)
+- `EXPORTED_FUNCTIONS` includes all functions you need to call from JavaScript
+
+### Invalid Export Names
+If you see errors like `invalid export name: cxxbridge1$exception`:
+- Use `EXPORT_ALL=0` and manually specify functions in `exports.txt`
+- Avoid using `EXPORT_ALL=1` with auto-generated export lists that may contain `$` symbols
+
+### CMake Compiler Detection Errors
+If CMake fails to detect the compiler:
+- Ensure `CC` and `CXX` environment variables point to `emcc` and `em++`
+- The Emscripten toolchain should be automatically detected by `cmake-rs`
+
+## References
+
+- [Emscripten Documentation](https://emscripten.org/docs/getting_started/index.html)
+- [Rust and WebAssembly](https://rustwasm.github.io/docs/book/)
+- [cxx crate documentation](https://cxx.rs/)
+
diff --git a/graph-wasm/_build_env b/graph-wasm/_build_env
new file mode 100644
index 0000000000..a3617193c4
--- /dev/null
+++ b/graph-wasm/_build_env
@@ -0,0 +1,105 @@
+#!/usr/bin/env bash
+
+# --------------------
+# Build configuration
+# --------------------
+
+_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
+export CURRENT_VERSION="${CURRENT_VERSION:-develop}"
+export BUILD_NAME="${BUILD_NAME:-graph-wasm}"
+export CARGO_BUILD_TARGET="${CARGO_BUILD_TARGET:-wasm32-unknown-emscripten}"
+# Keep the emscripten cache in-repo so system cache cleaners do not wipe it.
+export EM_CACHE="${EM_CACHE:-${_SCRIPT_DIR}/.emsdk_cache}"
+export CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-${_SCRIPT_DIR}/target}"
+if [[ -z "${CARGO_INCREMENTAL:-}" && "${NODE_ENV:-}" != "production" ]]; then
+ export CARGO_INCREMENTAL=1
+fi
+if [[ -z "${RUSTC_WRAPPER:-}" ]] && command -v sccache >/dev/null 2>&1; then
+ export RUSTC_WRAPPER=sccache
+fi
+export CRATE_CC_NO_DEFAULTS=1
+
+# BUILD_MODE
+if [[ "${NODE_ENV:-}" == "production" ]]; then
+ BUILD_MODE=release
+else
+ BUILD_MODE="${1:-debug}"
+fi
+export BUILD_MODE
+
+# --------------------
+# Emscripten memory
+# --------------------
+
+export EM_INITIAL_HEAP=$((256 * 1024 * 1024))
+export EM_MAXIMUM_MEMORY=$((4 * 1024 * 1024 * 1024))
+export EM_MEMORY_GROWTH_GEOMETRIC_STEP=0.8
+export EM_MALLOC=dlmalloc
+
+# --------------------
+# Flags
+# --------------------
+
+EMCC_COMMON_FLAGS=(
+ --no-entry
+ -sASSERTIONS=1
+ -sALLOW_TABLE_GROWTH=1
+ -sALLOW_MEMORY_GROWTH=1
+ -sINITIAL_HEAP=$EM_INITIAL_HEAP
+ -sMEMORY_GROWTH_GEOMETRIC_STEP=$EM_MEMORY_GROWTH_GEOMETRIC_STEP
+ -sMAXIMUM_MEMORY=$EM_MAXIMUM_MEMORY
+ -sERROR_ON_UNDEFINED_SYMBOLS=0
+ -sDISABLE_EXCEPTION_CATCHING=0
+ -sEXPORT_NAME=createGraphModule
+ -sEXPORTED_RUNTIME_METHODS=stringToUTF8,HEAPU8
+ -sENVIRONMENT=web
+ -sMODULARIZE=1
+ -sEXPORT_ES6=1
+)
+
+export RUSTFLAGS="-C link-arg=-sEXPORTED_FUNCTIONS=@${_SCRIPT_DIR}/exports.txt -C link-arg=-sEXPORT_ALL=0"
+
+# Mode-specific flags
+if [[ "$BUILD_MODE" == "release" ]]; then
+ export EMCC_CFLAGS="-Os ${EMCC_COMMON_FLAGS[*]}"
+ CARGO_PARAMS=(--release "${@:2}")
+else
+ export EMCC_CFLAGS="-g -sVERBOSE=1 -sMALLOC=$EM_MALLOC ${EMCC_COMMON_FLAGS[*]}"
+ CARGO_PARAMS=("${@:2}")
+fi
+
+export CARGO_PARAMS
+
+# --------------------
+# Tasks
+# --------------------
+
+clean() {
+ cargo clean
+}
+
+setup() {
+ :
+}
+
+build() {
+ cargo build "${CARGO_PARAMS[@]}"
+}
+
+copy_artifacts() {
+ local dest=$1
+ local base="target/$CARGO_BUILD_TARGET/$BUILD_MODE"
+
+ mkdir -p "$dest"
+
+ cp "$base/graph_wasm.js" "$dest/$BUILD_NAME.js"
+ cp "$base/graph_wasm.wasm" "$dest/$BUILD_NAME.wasm"
+
+ sed -i "s/graph_wasm.wasm/$BUILD_NAME.wasm?version=$CURRENT_VERSION/g" \
+ "$dest/$BUILD_NAME.js"
+}
+
+copy_shared_artifact() {
+ :
+}
diff --git a/graph-wasm/build b/graph-wasm/build
new file mode 100755
index 0000000000..1107f12c3f
--- /dev/null
+++ b/graph-wasm/build
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+
+EMSDK_QUIET=1 . /opt/emsdk/emsdk_env.sh
+
+_SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd);
+pushd $_SCRIPT_DIR;
+
+. ./_build_env
+
+set -ex;
+
+setup;
+build;
+copy_artifacts "../frontend/resources/public/js";
+copy_shared_artifact;
+
+exit $?;
+
+popd
+
diff --git a/graph-wasm/build.log b/graph-wasm/build.log
new file mode 100644
index 0000000000..aab4f223df
--- /dev/null
+++ b/graph-wasm/build.log
@@ -0,0 +1,26 @@
+~/penpot/graph-wasm ~/penpot/graph-wasm
+./_build_env: line 60: export: `CXX_wasm32-unknown-emscripten=/home/penpot/penpot/graph-wasm/wrapper-em++.sh': not a valid identifier
+./_build_env: line 110: export: `CXXFLAGS_wasm32-unknown-emscripten=-ffunction-sections -fdata-sections -fexceptions --target=wasm32-unknown-emscripten': not a valid identifier
++ setup
++ true
++ build
++ cargo build
+ Compiling graph v0.1.0 (/home/penpot/penpot/graph-wasm)
+warning: unused import: `Error`
+ --> src/main.rs:1:48
+ |
+1 | use lbug::{Database, Connection, SystemConfig, Error};
+ | ^^^^^
+ |
+ = note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default
+
+warning: variable does not need to be mutable
+ --> src/main.rs:23:9
+ |
+23 | let mut conn = match Connection::new(&db) {
+ | ----^^^^
+ | |
+ | help: remove this `mut`
+ |
+ = note: `#[warn(unused_mut)]` (part of `#[warn(unused)]`) on by default
+
diff --git a/graph-wasm/build.rs b/graph-wasm/build.rs
new file mode 100644
index 0000000000..1f151b4e94
--- /dev/null
+++ b/graph-wasm/build.rs
@@ -0,0 +1,2 @@
+// We need this empty script so OUT_DIR is automatically set
+fn main() {}
diff --git a/graph-wasm/exports.txt b/graph-wasm/exports.txt
new file mode 100644
index 0000000000..cc71b6d709
--- /dev/null
+++ b/graph-wasm/exports.txt
@@ -0,0 +1,11 @@
+_hello
+_generate_db
+_init
+_search_similar_shapes
+_free_similar_shapes_buffer
+_set_shape_parent
+_set_shape_selrect
+_set_shape_type
+_use_shape
+
+
diff --git a/graph-wasm/lbug-0.12.2/.gitignore b/graph-wasm/lbug-0.12.2/.gitignore
new file mode 100644
index 0000000000..391ed4d660
--- /dev/null
+++ b/graph-wasm/lbug-0.12.2/.gitignore
@@ -0,0 +1,5 @@
+target/
+debug/
+
+**/*.rs.bk
+
diff --git a/graph-wasm/lbug-0.12.2/Cargo.lock b/graph-wasm/lbug-0.12.2/Cargo.lock
new file mode 100644
index 0000000000..14cd17d92d
--- /dev/null
+++ b/graph-wasm/lbug-0.12.2/Cargo.lock
@@ -0,0 +1,1234 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "ahash"
+version = "0.8.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
+dependencies = [
+ "cfg-if",
+ "const-random",
+ "getrandom 0.3.3",
+ "once_cell",
+ "version_check",
+ "zerocopy",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
+
+[[package]]
+name = "anyhow"
+version = "1.0.99"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
+
+[[package]]
+name = "arrayvec"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+
+[[package]]
+name = "arrow"
+version = "55.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3f15b4c6b148206ff3a2b35002e08929c2462467b62b9c02036d9c34f9ef994"
+dependencies = [
+ "arrow-arith",
+ "arrow-array",
+ "arrow-buffer",
+ "arrow-cast",
+ "arrow-data",
+ "arrow-ord",
+ "arrow-row",
+ "arrow-schema",
+ "arrow-select",
+ "arrow-string",
+]
+
+[[package]]
+name = "arrow-arith"
+version = "55.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30feb679425110209ae35c3fbf82404a39a4c0436bb3ec36164d8bffed2a4ce4"
+dependencies = [
+ "arrow-array",
+ "arrow-buffer",
+ "arrow-data",
+ "arrow-schema",
+ "chrono",
+ "num",
+]
+
+[[package]]
+name = "arrow-array"
+version = "55.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70732f04d285d49054a48b72c54f791bb3424abae92d27aafdf776c98af161c8"
+dependencies = [
+ "ahash",
+ "arrow-buffer",
+ "arrow-data",
+ "arrow-schema",
+ "chrono",
+ "half",
+ "hashbrown",
+ "num",
+]
+
+[[package]]
+name = "arrow-buffer"
+version = "55.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "169b1d5d6cb390dd92ce582b06b23815c7953e9dfaaea75556e89d890d19993d"
+dependencies = [
+ "bytes",
+ "half",
+ "num",
+]
+
+[[package]]
+name = "arrow-cast"
+version = "55.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4f12eccc3e1c05a766cafb31f6a60a46c2f8efec9b74c6e0648766d30686af8"
+dependencies = [
+ "arrow-array",
+ "arrow-buffer",
+ "arrow-data",
+ "arrow-schema",
+ "arrow-select",
+ "atoi",
+ "base64",
+ "chrono",
+ "half",
+ "lexical-core",
+ "num",
+ "ryu",
+]
+
+[[package]]
+name = "arrow-data"
+version = "55.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de1ce212d803199684b658fc4ba55fb2d7e87b213de5af415308d2fee3619c2"
+dependencies = [
+ "arrow-buffer",
+ "arrow-schema",
+ "half",
+ "num",
+]
+
+[[package]]
+name = "arrow-ord"
+version = "55.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6506e3a059e3be23023f587f79c82ef0bcf6d293587e3272d20f2d30b969b5a7"
+dependencies = [
+ "arrow-array",
+ "arrow-buffer",
+ "arrow-data",
+ "arrow-schema",
+ "arrow-select",
+]
+
+[[package]]
+name = "arrow-row"
+version = "55.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52bf7393166beaf79b4bed9bfdf19e97472af32ce5b6b48169d321518a08cae2"
+dependencies = [
+ "arrow-array",
+ "arrow-buffer",
+ "arrow-data",
+ "arrow-schema",
+ "half",
+]
+
+[[package]]
+name = "arrow-schema"
+version = "55.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af7686986a3bf2254c9fb130c623cdcb2f8e1f15763e7c71c310f0834da3d292"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "arrow-select"
+version = "55.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd2b45757d6a2373faa3352d02ff5b54b098f5e21dccebc45a21806bc34501e5"
+dependencies = [
+ "ahash",
+ "arrow-array",
+ "arrow-buffer",
+ "arrow-data",
+ "arrow-schema",
+ "num",
+]
+
+[[package]]
+name = "arrow-string"
+version = "55.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0377d532850babb4d927a06294314b316e23311503ed580ec6ce6a0158f49d40"
+dependencies = [
+ "arrow-array",
+ "arrow-buffer",
+ "arrow-data",
+ "arrow-schema",
+ "arrow-select",
+ "memchr",
+ "num",
+ "regex",
+ "regex-syntax",
+]
+
+[[package]]
+name = "atoi"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "bitflags"
+version = "2.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
+
+[[package]]
+name = "bumpalo"
+version = "3.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
+
+[[package]]
+name = "bytes"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
+
+[[package]]
+name = "cc"
+version = "1.2.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5252b3d2648e5eedbc1a6f501e3c795e07025c1e93bbf8bbdd6eef7f447a6d54"
+dependencies = [
+ "find-msvc-tools",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
+
+[[package]]
+name = "chrono"
+version = "0.4.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
+dependencies = [
+ "iana-time-zone",
+ "num-traits",
+ "windows-link 0.2.0",
+]
+
+[[package]]
+name = "clap"
+version = "4.5.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931"
+dependencies = [
+ "clap_builder",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6"
+dependencies = [
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
+
+[[package]]
+name = "cmake"
+version = "0.1.54"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "codespan-reporting"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
+dependencies = [
+ "termcolor",
+ "unicode-width",
+]
+
+[[package]]
+name = "const-random"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
+dependencies = [
+ "const-random-macro",
+]
+
+[[package]]
+name = "const-random-macro"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
+dependencies = [
+ "getrandom 0.2.16",
+ "once_cell",
+ "tiny-keccak",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "crunchy"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
+
+[[package]]
+name = "cxx"
+version = "1.0.138"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3956d60afa98653c5a57f60d7056edd513bfe0307ef6fb06f6167400c3884459"
+dependencies = [
+ "cc",
+ "cxxbridge-cmd",
+ "cxxbridge-flags",
+ "cxxbridge-macro",
+ "foldhash",
+ "link-cplusplus",
+]
+
+[[package]]
+name = "cxx-build"
+version = "1.0.138"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a4b7522f539fe056f1d6fc8577d8ab731451f6f33a89b1e5912e22b76c553e7"
+dependencies = [
+ "cc",
+ "codespan-reporting",
+ "proc-macro2",
+ "quote",
+ "scratch",
+ "syn",
+]
+
+[[package]]
+name = "cxxbridge-cmd"
+version = "1.0.138"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f01e92ab4ce9fd4d16e3bb11b158d98cbdcca803c1417aa43130a6526fbf208"
+dependencies = [
+ "clap",
+ "codespan-reporting",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "cxxbridge-flags"
+version = "1.0.138"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c41cbfab344869e70998b388923f7d1266588f56c8ca284abf259b1c1ffc695"
+
+[[package]]
+name = "cxxbridge-macro"
+version = "1.0.138"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88d82a2f759f0ad3eae43b96604efd42b1d4729a35a6f2dc7bdb797ae25d9284"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rustversion",
+ "syn",
+]
+
+[[package]]
+name = "deranged"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "errno"
+version = "0.3.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
+dependencies = [
+ "libc",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d"
+
+[[package]]
+name = "foldhash"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+
+[[package]]
+name = "getrandom"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.11.1+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasi 0.14.4+wasi-0.2.4",
+]
+
+[[package]]
+name = "half"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9"
+dependencies = [
+ "cfg-if",
+ "crunchy",
+ "num-traits",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.15.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "log",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lbug"
+version = "0.12.2"
+dependencies = [
+ "anyhow",
+ "arrow",
+ "cmake",
+ "cxx",
+ "cxx-build",
+ "rust_decimal",
+ "rust_decimal_macros",
+ "rustversion",
+ "tempfile",
+ "time",
+ "uuid",
+]
+
+[[package]]
+name = "lexical-core"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b765c31809609075565a70b4b71402281283aeda7ecaf4818ac14a7b2ade8958"
+dependencies = [
+ "lexical-parse-float",
+ "lexical-parse-integer",
+ "lexical-util",
+ "lexical-write-float",
+ "lexical-write-integer",
+]
+
+[[package]]
+name = "lexical-parse-float"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de6f9cb01fb0b08060209a057c048fcbab8717b4c1ecd2eac66ebfe39a65b0f2"
+dependencies = [
+ "lexical-parse-integer",
+ "lexical-util",
+ "static_assertions",
+]
+
+[[package]]
+name = "lexical-parse-integer"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72207aae22fc0a121ba7b6d479e42cbfea549af1479c3f3a4f12c70dd66df12e"
+dependencies = [
+ "lexical-util",
+ "static_assertions",
+]
+
+[[package]]
+name = "lexical-util"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a82e24bf537fd24c177ffbbdc6ebcc8d54732c35b50a3f28cc3f4e4c949a0b3"
+dependencies = [
+ "static_assertions",
+]
+
+[[package]]
+name = "lexical-write-float"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5afc668a27f460fb45a81a757b6bf2f43c2d7e30cb5a2dcd3abf294c78d62bd"
+dependencies = [
+ "lexical-util",
+ "lexical-write-integer",
+ "static_assertions",
+]
+
+[[package]]
+name = "lexical-write-integer"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "629ddff1a914a836fb245616a7888b62903aae58fa771e1d83943035efa0f978"
+dependencies = [
+ "lexical-util",
+ "static_assertions",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.175"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
+
+[[package]]
+name = "libm"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
+
+[[package]]
+name = "link-cplusplus"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f78c730aaa7d0b9336a299029ea49f9ee53b0ed06e9202e8cb7db9bae7b8c82"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
+
+[[package]]
+name = "log"
+version = "0.4.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
+
+[[package]]
+name = "memchr"
+version = "2.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
+
+[[package]]
+name = "num"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
+dependencies = [
+ "num-bigint",
+ "num-complex",
+ "num-integer",
+ "num-iter",
+ "num-rational",
+ "num-traits",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-complex"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-conv"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-iter"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
+dependencies = [
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+ "libm",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "regex"
+version = "1.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
+
+[[package]]
+name = "rust_decimal"
+version = "1.37.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b203a6425500a03e0919c42d3c47caca51e79f1132046626d2c8871c5092035d"
+dependencies = [
+ "arrayvec",
+ "num-traits",
+]
+
+[[package]]
+name = "rust_decimal_macros"
+version = "1.37.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6268b74858287e1a062271b988a0c534bf85bbeb567fe09331bf40ed78113d5"
+dependencies = [
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "rustix"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+
+[[package]]
+name = "ryu"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+
+[[package]]
+name = "scratch"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d68f2ec51b097e4c1a75b681a8bec621909b5e91f15bb7b840c4f2f7b01148b2"
+
+[[package]]
+name = "serde"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "syn"
+version = "2.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e"
+dependencies = [
+ "fastrand",
+ "getrandom 0.3.3",
+ "once_cell",
+ "rustix",
+ "windows-sys 0.60.2",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "time"
+version = "0.3.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83bde6f1ec10e72d583d91623c939f623002284ef622b87de38cfd546cbf2031"
+dependencies = [
+ "deranged",
+ "num-conv",
+ "powerfmt",
+ "serde",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
+
+[[package]]
+name = "time-macros"
+version = "0.2.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
+[[package]]
+name = "tiny-keccak"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
+dependencies = [
+ "crunchy",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
+
+[[package]]
+name = "uuid"
+version = "1.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasi"
+version = "0.14.4+wasi-0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88a5f4a424faf49c3c2c344f166f0662341d470ea185e939657aaff130f0ec4a"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb"
+dependencies = [
+ "bumpalo",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.101"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "winapi-util"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
+dependencies = [
+ "windows-sys 0.61.0",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link 0.1.3",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
+
+[[package]]
+name = "windows-link"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
+
+[[package]]
+name = "windows-result"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
+dependencies = [
+ "windows-link 0.1.3",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
+dependencies = [
+ "windows-link 0.1.3",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.61.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa"
+dependencies = [
+ "windows-link 0.2.0",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.53.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
+dependencies = [
+ "windows-link 0.1.3",
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.53.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
+
+[[package]]
+name = "wit-bindgen"
+version = "0.45.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36"
+
+[[package]]
+name = "zerocopy"
+version = "0.8.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
diff --git a/graph-wasm/lbug-0.12.2/Cargo.toml b/graph-wasm/lbug-0.12.2/Cargo.toml
new file mode 100644
index 0000000000..ea839f84c3
--- /dev/null
+++ b/graph-wasm/lbug-0.12.2/Cargo.toml
@@ -0,0 +1,144 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+rust-version = "1.81"
+name = "lbug"
+version = "0.12.2"
+build = "build.rs"
+include = [
+ "build.rs",
+ "/src",
+ "/include",
+ "/lbug-src/src",
+ "/lbug-src/cmake",
+ "/lbug-src/third_party",
+ "/lbug-src/CMakeLists.txt",
+ "/lbug-src/tools/CMakeLists.txt",
+]
+autolib = false
+autobins = false
+autoexamples = false
+autotests = false
+autobenches = false
+description = "An in-process property graph database management system built for query speed and scalability"
+homepage = "https://ladybugdb.com/"
+readme = "lbug-src/README.md"
+keywords = [
+ "database",
+ "graph",
+ "ffi",
+]
+categories = ["database"]
+license = "MIT"
+repository = "https://github.com/lbugdb/lbug"
+
+[package.metadata.docs.rs]
+all-features = true
+
+[features]
+arrow = ["dep:arrow"]
+default = []
+extension_tests = []
+
+[lib]
+name = "lbug"
+path = "src/lib.rs"
+
+[dependencies.arrow]
+version = "55"
+features = ["ffi"]
+optional = true
+default-features = false
+
+[dependencies.cxx]
+version = "=1.0.138"
+
+[dependencies.rust_decimal]
+version = "1.37"
+default-features = false
+
+[dependencies.time]
+version = "0.3"
+
+[dependencies.uuid]
+version = "1.6"
+
+[dev-dependencies.anyhow]
+version = "1"
+
+[dev-dependencies.rust_decimal_macros]
+version = "1.37"
+
+[dev-dependencies.tempfile]
+version = "3"
+
+[dev-dependencies.time]
+version = "0.3"
+features = ["macros"]
+
+[build-dependencies.cmake]
+version = "0.1"
+
+[build-dependencies.cxx-build]
+version = "=1.0.138"
+
+[build-dependencies.rustversion]
+version = "1"
+
+[lints.clippy]
+inline_always = "allow"
+missing_errors_doc = "allow"
+missing_panics_doc = "allow"
+module_name_repetitions = "allow"
+must_use_candidate = "allow"
+needless_pass_by_value = "allow"
+redundant_closure_for_method_calls = "allow"
+return_self_not_must_use = "allow"
+similar_names = "allow"
+struct_excessive_bools = "allow"
+too_many_arguments = "allow"
+too_many_lines = "allow"
+type_complexity = "allow"
+unreadable_literal = "allow"
+
+[lints.clippy.cargo]
+level = "warn"
+priority = -1
+
+[lints.clippy.complexity]
+level = "warn"
+priority = -1
+
+[lints.clippy.correctness]
+level = "warn"
+priority = -1
+
+[lints.clippy.pedantic]
+level = "warn"
+priority = -1
+
+[lints.clippy.perf]
+level = "warn"
+priority = -1
+
+[lints.clippy.style]
+level = "warn"
+priority = -1
+
+[lints.clippy.suspicious]
+level = "warn"
+priority = -1
+
+[profile.relwithdebinfo]
+debug = 2
+inherits = "release"
diff --git a/graph-wasm/lbug-0.12.2/build.rs b/graph-wasm/lbug-0.12.2/build.rs
new file mode 100644
index 0000000000..23f644a5af
--- /dev/null
+++ b/graph-wasm/lbug-0.12.2/build.rs
@@ -0,0 +1,283 @@
+use std::env;
+use std::path::{Path, PathBuf};
+
+fn link_mode() -> &'static str {
+ if env::var("LBUG_SHARED").is_ok() {
+ "dylib"
+ } else {
+ "static"
+ }
+}
+
+fn get_target() -> String {
+ env::var("PROFILE").unwrap()
+}
+
+fn is_wasm_emscripten() -> bool {
+ env::var("TARGET")
+ .map(|t| t == "wasm32-unknown-emscripten")
+ .unwrap_or(false)
+}
+
+fn link_libraries() {
+ // For wasm32-unknown-emscripten, we need to link lbug and all its dependencies
+ // These are built by CMake and need to be linked here
+ if is_wasm_emscripten() {
+ // Link all dependencies first (built by CMake)
+ for lib in [
+ "utf8proc",
+ "antlr4_cypher",
+ "antlr4_runtime",
+ "re2",
+ "fastpfor",
+ "parquet",
+ "thrift",
+ "snappy",
+ "zstd",
+ "miniz",
+ "mbedtls",
+ "brotlidec",
+ "brotlicommon",
+ "lz4",
+ "roaring_bitmap",
+ "simsimd",
+ ] {
+ println!("cargo:rustc-link-lib=static={lib}");
+ }
+ // Link the lbug static library (built by CMake)
+ println!("cargo:rustc-link-lib=static=lbug");
+ // Don't link system libraries for wasm (they're handled by Emscripten)
+ return;
+ }
+
+ // This also needs to be set by any crates using it if they want to use extensions
+ if !cfg!(windows) && link_mode() == "static" {
+ println!("cargo:rustc-link-arg=-rdynamic");
+ }
+ if cfg!(windows) && link_mode() == "dylib" {
+ println!("cargo:rustc-link-lib=dylib=lbug_shared");
+ } else if link_mode() == "dylib" {
+ println!("cargo:rustc-link-lib={}=lbug", link_mode());
+ } else if rustversion::cfg!(since(1.82)) {
+ println!("cargo:rustc-link-lib=static:+whole-archive=lbug");
+ } else {
+ println!("cargo:rustc-link-lib=static=lbug");
+ }
+ if link_mode() == "static" {
+ if cfg!(windows) {
+ println!("cargo:rustc-link-lib=dylib=msvcrt");
+ println!("cargo:rustc-link-lib=dylib=shell32");
+ println!("cargo:rustc-link-lib=dylib=ole32");
+ } else if cfg!(target_os = "macos") {
+ println!("cargo:rustc-link-lib=dylib=c++");
+ } else {
+ println!("cargo:rustc-link-lib=dylib=stdc++");
+ }
+
+ for lib in [
+ "utf8proc",
+ "antlr4_cypher",
+ "antlr4_runtime",
+ "re2",
+ "fastpfor",
+ "parquet",
+ "thrift",
+ "snappy",
+ "zstd",
+ "miniz",
+ "mbedtls",
+ "brotlidec",
+ "brotlicommon",
+ "lz4",
+ "roaring_bitmap",
+ "simsimd",
+ ] {
+ if rustversion::cfg!(since(1.82)) {
+ println!("cargo:rustc-link-lib=static:+whole-archive={lib}");
+ } else {
+ println!("cargo:rustc-link-lib=static={lib}");
+ }
+ }
+ }
+}
+
+fn build_bundled_cmake() -> Vec {
+ let lbug_root = {
+ let root = Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("lbug-src");
+ if root.is_symlink() || root.is_dir() {
+ root
+ } else {
+ // If the path is not directory, this is probably an in-source build on windows where the
+ // symlink is unreadable.
+ Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("../..")
+ }
+ };
+
+ let mut build = cmake::Config::new(&lbug_root);
+ build
+ .no_build_target(true)
+ .define("BUILD_SHELL", "OFF")
+ .define("BUILD_SINGLE_FILE_HEADER", "OFF")
+ .define("AUTO_UPDATE_GRAMMAR", "OFF");
+
+ // Configure for wasm32-unknown-emscripten
+ if is_wasm_emscripten() {
+ // Same configuration as ladybug/tools/wasm build
+ build.define("SINGLE_THREADED", "TRUE");
+ // cmake-rs should automatically detect emscripten toolchain when CC/CXX point to emcc/em++
+ } else if cfg!(windows) {
+ build.generator("Ninja");
+ build.cxxflag("/EHsc");
+ build.define("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreadedDLL");
+ build.define("CMAKE_POLICY_DEFAULT_CMP0091", "NEW");
+ }
+ if let Ok(jobs) = env::var("NUM_JOBS") {
+ // SAFETY: Setting environment variables in build scripts is safe
+ unsafe {
+ env::set_var("CMAKE_BUILD_PARALLEL_LEVEL", jobs);
+ }
+ }
+ let build_dir = build.build();
+
+ let lbug_lib_path = build_dir.join("build").join("src");
+ println!("cargo:rustc-link-search=native={}", lbug_lib_path.display());
+
+ for dir in [
+ "utf8proc",
+ "antlr4_cypher",
+ "antlr4_runtime",
+ "re2",
+ "brotli",
+ "alp",
+ "fastpfor",
+ "parquet",
+ "thrift",
+ "snappy",
+ "zstd",
+ "miniz",
+ "mbedtls",
+ "lz4",
+ "roaring_bitmap",
+ "simsimd",
+ ] {
+ let lib_path = build_dir
+ .join("build")
+ .join("third_party")
+ .join(dir)
+ .canonicalize()
+ .unwrap_or_else(|_| {
+ panic!(
+ "Could not find {}/build/third_party/{}",
+ build_dir.display(),
+ dir
+ )
+ });
+ println!("cargo:rustc-link-search=native={}", lib_path.display());
+ }
+
+ vec![
+ lbug_root.join("src/include"),
+ build_dir.join("build/src"),
+ build_dir.join("build/src/include"),
+ lbug_root.join("third_party/nlohmann_json"),
+ lbug_root.join("third_party/fastpfor"),
+ lbug_root.join("third_party/alp/include"),
+ ]
+}
+
+fn build_ffi(
+ bridge_file: &str,
+ out_name: &str,
+ source_file: &str,
+ bundled: bool,
+ include_paths: &Vec,
+) {
+ let mut build = cxx_build::bridge(bridge_file);
+ build.file(source_file);
+
+ if bundled {
+ build.define("LBUG_BUNDLED", None);
+ }
+ if get_target() == "debug" || get_target() == "relwithdebinfo" {
+ build.define("ENABLE_RUNTIME_CHECKS", "1");
+ }
+ if link_mode() == "static" {
+ build.define("LBUG_STATIC_DEFINE", None);
+ }
+
+ build.includes(include_paths);
+
+ println!("cargo:rerun-if-env-changed=LBUG_SHARED");
+
+ println!("cargo:rerun-if-changed=include/lbug_rs.h");
+ println!("cargo:rerun-if-changed=src/lbug_rs.cpp");
+ // Note that this should match the lbug-src/* entries in the package.include list in Cargo.toml
+ // Unfortunately they appear to need to be specified individually since the symlink is
+ // considered to be changed each time.
+ println!("cargo:rerun-if-changed=lbug-src/src");
+ println!("cargo:rerun-if-changed=lbug-src/cmake");
+ println!("cargo:rerun-if-changed=lbug-src/third_party");
+ println!("cargo:rerun-if-changed=lbug-src/CMakeLists.txt");
+ println!("cargo:rerun-if-changed=lbug-src/tools/CMakeLists.txt");
+
+ if is_wasm_emscripten() {
+ // For emscripten, use C++20 and enable exceptions
+ build.flag("-std=c++20");
+ build.flag("-fexceptions");
+ // Note: -sDISABLE_EXCEPTION_CATCHING=0 is a linker flag, not a compiler flag
+ // It should be set via EMCC_CFLAGS environment variable or cargo rustc-link-arg
+ } else if cfg!(windows) {
+ build.flag("/std:c++20");
+ build.flag("/MD");
+ } else {
+ build.flag("-std=c++2a");
+ }
+ build.compile(out_name);
+}
+
+fn main() {
+ if env::var("DOCS_RS").is_ok() {
+ // Do nothing; we're just building docs and don't need the C++ library
+ return;
+ }
+
+ let mut bundled = false;
+ let mut include_paths =
+ vec![Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap()).join("include")];
+
+ if let (Ok(lbug_lib_dir), Ok(lbug_include)) =
+ (env::var("LBUG_LIBRARY_DIR"), env::var("LBUG_INCLUDE_DIR"))
+ {
+ println!("cargo:rustc-link-search=native={lbug_lib_dir}");
+ println!("cargo:rustc-link-arg=-Wl,-rpath,{lbug_lib_dir}");
+ include_paths.push(Path::new(&lbug_include).to_path_buf());
+ } else {
+ include_paths.extend(build_bundled_cmake());
+ bundled = true;
+ }
+ // For wasm, we need to link libraries after building FFI to ensure proper symbol resolution
+ if !is_wasm_emscripten() && link_mode() == "static" {
+ link_libraries();
+ }
+ build_ffi(
+ "src/ffi.rs",
+ "lbug_rs",
+ "src/lbug_rs.cpp",
+ bundled,
+ &include_paths,
+ );
+
+ if cfg!(feature = "arrow") {
+ build_ffi(
+ "src/ffi/arrow.rs",
+ "lbug_arrow_rs",
+ "src/lbug_arrow.cpp",
+ bundled,
+ &include_paths,
+ );
+ }
+ // For wasm, link libraries after FFI; for dylib, link after FFI
+ if is_wasm_emscripten() || link_mode() == "dylib" {
+ link_libraries();
+ }
+}
diff --git a/graph-wasm/lbug-0.12.2/include/lbug_arrow.h b/graph-wasm/lbug-0.12.2/include/lbug_arrow.h
new file mode 100644
index 0000000000..a3587a7276
--- /dev/null
+++ b/graph-wasm/lbug-0.12.2/include/lbug_arrow.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "rust/cxx.h"
+#ifdef LBUG_BUNDLED
+#include "main/lbug.h"
+#else
+#include
+#endif
+
+namespace lbug_arrow {
+
+ArrowSchema query_result_get_arrow_schema(const lbug::main::QueryResult& result);
+ArrowArray query_result_get_next_arrow_chunk(lbug::main::QueryResult& result, uint64_t chunkSize);
+
+} // namespace lbug_arrow
diff --git a/graph-wasm/lbug-0.12.2/include/lbug_rs.h b/graph-wasm/lbug-0.12.2/include/lbug_rs.h
new file mode 100644
index 0000000000..64006650cd
--- /dev/null
+++ b/graph-wasm/lbug-0.12.2/include/lbug_rs.h
@@ -0,0 +1,243 @@
+#pragma once
+
+#include
+#include
+
+#include "rust/cxx.h"
+#ifdef LBUG_BUNDLED
+#include "common/type_utils.h"
+#include "common/types/int128_t.h"
+#include "common/types/types.h"
+#include "common/types/value/nested.h"
+#include "common/types/value/node.h"
+#include "common/types/value/recursive_rel.h"
+#include "common/types/value/rel.h"
+#include "common/types/value/value.h"
+#include "main/lbug.h"
+#include "storage/storage_version_info.h"
+#else
+#include
+#endif
+
+namespace lbug_rs {
+
+struct TypeListBuilder {
+ std::vector types;
+
+ void insert(std::unique_ptr type) {
+ types.push_back(std::move(*type));
+ }
+};
+
+std::unique_ptr create_type_list();
+
+struct QueryParams {
+ std::unordered_map> inputParams;
+
+ void insert(const rust::Str key, std::unique_ptr value) {
+ inputParams.insert(std::make_pair(key, std::move(value)));
+ }
+};
+
+std::unique_ptr new_params();
+
+std::unique_ptr create_logical_type(lbug::common::LogicalTypeID id);
+std::unique_ptr create_logical_type_list(
+ std::unique_ptr childType);
+std::unique_ptr create_logical_type_array(
+ std::unique_ptr childType, uint64_t numElements);
+
+inline std::unique_ptr create_logical_type_struct(
+ const rust::Vec& fieldNames, std::unique_ptr fieldTypes) {
+ std::vector fields;
+ for (auto i = 0u; i < fieldNames.size(); i++) {
+ fields.emplace_back(std::string(fieldNames[i]), std::move(fieldTypes->types[i]));
+ }
+ return std::make_unique(
+ lbug::common::LogicalType::STRUCT(std::move(fields)));
+}
+inline std::unique_ptr create_logical_type_union(
+ const rust::Vec& fieldNames, std::unique_ptr fieldTypes) {
+ std::vector fields;
+ for (auto i = 0u; i < fieldNames.size(); i++) {
+ fields.emplace_back(std::string(fieldNames[i]), std::move(fieldTypes->types[i]));
+ }
+ return std::make_unique(
+ lbug::common::LogicalType::UNION(std::move(fields)));
+}
+std::unique_ptr create_logical_type_map(
+ std::unique_ptr keyType,
+ std::unique_ptr valueType);
+
+inline std::unique_ptr create_logical_type_decimal(uint32_t precision,
+ uint32_t scale) {
+ return std::make_unique(
+ lbug::common::LogicalType::DECIMAL(precision, scale));
+}
+
+std::unique_ptr logical_type_get_list_child_type(
+ const lbug::common::LogicalType& logicalType);
+std::unique_ptr logical_type_get_array_child_type(
+ const lbug::common::LogicalType& logicalType);
+uint64_t logical_type_get_array_num_elements(const lbug::common::LogicalType& logicalType);
+
+rust::Vec logical_type_get_struct_field_names(const lbug::common::LogicalType& value);
+std::unique_ptr> logical_type_get_struct_field_types(
+ const lbug::common::LogicalType& value);
+
+inline uint32_t logical_type_get_decimal_precision(const lbug::common::LogicalType& logicalType) {
+ return lbug::common::DecimalType::getPrecision(logicalType);
+}
+inline uint32_t logical_type_get_decimal_scale(const lbug::common::LogicalType& logicalType) {
+ return lbug::common::DecimalType::getScale(logicalType);
+}
+
+/* Database */
+std::unique_ptr new_database(std::string_view databasePath,
+ uint64_t bufferPoolSize, uint64_t maxNumThreads, bool enableCompression, bool readOnly,
+ uint64_t maxDBSize, bool autoCheckpoint, int64_t checkpointThreshold,
+ bool throwOnWalReplayFailure, bool enableChecksums);
+
+void database_set_logging_level(lbug::main::Database& database, const std::string& level);
+
+/* Connection */
+std::unique_ptr database_connect(lbug::main::Database& database);
+std::unique_ptr connection_execute(lbug::main::Connection& connection,
+ lbug::main::PreparedStatement& query, std::unique_ptr params);
+inline std::unique_ptr connection_query(lbug::main::Connection& connection,
+ std::string_view query) {
+ return connection.query(query);
+}
+
+/* PreparedStatement */
+rust::String prepared_statement_error_message(const lbug::main::PreparedStatement& statement);
+
+/* QueryResult */
+rust::String query_result_to_string(const lbug::main::QueryResult& result);
+rust::String query_result_get_error_message(const lbug::main::QueryResult& result);
+
+double query_result_get_compiling_time(const lbug::main::QueryResult& result);
+double query_result_get_execution_time(const lbug::main::QueryResult& result);
+
+std::unique_ptr> query_result_column_data_types(
+ const lbug::main::QueryResult& query_result);
+rust::Vec query_result_column_names(const lbug::main::QueryResult& query_result);
+
+/* NodeVal/RelVal */
+rust::String node_value_get_label_name(const lbug::common::Value& val);
+rust::String rel_value_get_label_name(const lbug::common::Value& val);
+
+size_t node_value_get_num_properties(const lbug::common::Value& value);
+size_t rel_value_get_num_properties(const lbug::common::Value& value);
+
+rust::String node_value_get_property_name(const lbug::common::Value& value, size_t index);
+rust::String rel_value_get_property_name(const lbug::common::Value& value, size_t index);
+
+const lbug::common::Value& node_value_get_property_value(const lbug::common::Value& value,
+ size_t index);
+const lbug::common::Value& rel_value_get_property_value(const lbug::common::Value& value,
+ size_t index);
+
+/* NodeVal */
+const lbug::common::Value& node_value_get_node_id(const lbug::common::Value& val);
+
+/* RelVal */
+const lbug::common::Value& rel_value_get_src_id(const lbug::common::Value& val);
+std::array rel_value_get_dst_id(const lbug::common::Value& val);
+
+/* RecursiveRel */
+const lbug::common::Value& recursive_rel_get_nodes(const lbug::common::Value& val);
+const lbug::common::Value& recursive_rel_get_rels(const lbug::common::Value& val);
+
+/* FlatTuple */
+const lbug::common::Value& flat_tuple_get_value(const lbug::processor::FlatTuple& flatTuple,
+ uint32_t index);
+
+/* Value */
+const std::string& value_get_string(const lbug::common::Value& value);
+
+template
+std::unique_ptr value_get_unique(const lbug::common::Value& value) {
+ return std::make_unique(value.getValue());
+}
+
+int64_t value_get_interval_secs(const lbug::common::Value& value);
+int32_t value_get_interval_micros(const lbug::common::Value& value);
+int32_t value_get_date_days(const lbug::common::Value& value);
+int64_t value_get_timestamp_ns(const lbug::common::Value& value);
+int64_t value_get_timestamp_ms(const lbug::common::Value& value);
+int64_t value_get_timestamp_sec(const lbug::common::Value& value);
+int64_t value_get_timestamp_micros(const lbug::common::Value& value);
+int64_t value_get_timestamp_tz(const lbug::common::Value& value);
+std::array value_get_int128_t(const lbug::common::Value& value);
+std::array value_get_internal_id(const lbug::common::Value& value);
+uint32_t value_get_children_size(const lbug::common::Value& value);
+const lbug::common::Value& value_get_child(const lbug::common::Value& value, uint32_t index);
+lbug::common::LogicalTypeID value_get_data_type_id(const lbug::common::Value& value);
+const lbug::common::LogicalType& value_get_data_type(const lbug::common::Value& value);
+inline lbug::common::PhysicalTypeID value_get_physical_type(const lbug::common::Value& value) {
+ return value.getDataType().getPhysicalType();
+}
+rust::String value_to_string(const lbug::common::Value& val);
+
+std::unique_ptr create_value_string(lbug::common::LogicalTypeID typ,
+ const rust::Slice value);
+std::unique_ptr create_value_timestamp(const int64_t timestamp);
+std::unique_ptr create_value_timestamp_tz(const int64_t timestamp);
+std::unique_ptr create_value_timestamp_ns(const int64_t timestamp);
+std::unique_ptr create_value_timestamp_ms(const int64_t timestamp);
+std::unique_ptr create_value_timestamp_sec(const int64_t timestamp);
+inline std::unique_ptr create_value_date(const int32_t date) {
+ return std::make_unique(lbug::common::date_t(date));
+}
+std::unique_ptr create_value_interval(const int32_t months, const int32_t days,
+ const int64_t micros);
+std::unique_ptr create_value_null(
+ std::unique_ptr typ);
+std::unique_ptr create_value_int128_t(int64_t high, uint64_t low);
+std::unique_ptr create_value_internal_id(uint64_t offset, uint64_t table);
+
+inline std::unique_ptr create_value_uuid_t(int64_t high, uint64_t low) {
+ return std::make_unique(
+ lbug::common::ku_uuid_t{lbug::common::int128_t(low, high)});
+}
+
+template
+std::unique_ptr create_value(const T value) {
+ return std::make_unique(value);
+}
+inline std::unique_ptr create_value_decimal(int64_t high, uint64_t low,
+ uint32_t scale, uint32_t precision) {
+ auto value =
+ std::make_unique(lbug::common::LogicalType::DECIMAL(precision, scale),
+ std::vector>{});
+ auto i128 = lbug::common::int128_t(low, high);
+ lbug::common::TypeUtils::visit(
+ value->getDataType().getPhysicalType(),
+ [&](lbug::common::int128_t) { value->val.int128Val = i128; },
+ [&](int64_t) { value->val.int64Val = static_cast(i128); },
+ [&](int32_t) { value->val.int32Val = static_cast(i128); },
+ [&](int16_t) { value->val.int16Val = static_cast(i128); },
+ [](auto) { KU_UNREACHABLE; });
+ return value;
+}
+
+struct ValueListBuilder {
+ std::vector> values;
+
+ void insert(std::unique_ptr value) { values.push_back(std::move(value)); }
+};
+
+std::unique_ptr get_list_value(std::unique_ptr typ,
+ std::unique_ptr value);
+std::unique_ptr create_list();
+
+inline std::string_view string_view_from_str(rust::Str s) {
+ return {s.data(), s.size()};
+}
+
+inline lbug::storage::storage_version_t get_storage_version() {
+ return lbug::storage::StorageVersionInfo::getStorageVersion();
+}
+
+} // namespace lbug_rs
diff --git a/graph-wasm/lbug-0.12.2/lbug-src/CMakeLists.txt b/graph-wasm/lbug-0.12.2/lbug-src/CMakeLists.txt
new file mode 100644
index 0000000000..90890a5b51
--- /dev/null
+++ b/graph-wasm/lbug-0.12.2/lbug-src/CMakeLists.txt
@@ -0,0 +1,454 @@
+cmake_minimum_required(VERSION 3.15)
+
+project(Lbug VERSION 0.12.2 LANGUAGES CXX C)
+
+option(SINGLE_THREADED "Single-threaded mode" FALSE)
+if(SINGLE_THREADED)
+ set(__SINGLE_THREADED__ TRUE)
+ add_compile_definitions(__SINGLE_THREADED__)
+ message(STATUS "Single-threaded mode is enabled")
+else()
+ message(STATUS "Multi-threaded mode is enabled: CMAKE_BUILD_PARALLEL_LEVEL=$ENV{CMAKE_BUILD_PARALLEL_LEVEL}")
+ find_package(Threads REQUIRED)
+endif()
+
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
+set(CMAKE_CXX_VISIBILITY_PRESET hidden)
+set(CMAKE_C_VISIBILITY_PRESET hidden)
+set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
+set(CMAKE_FIND_PACKAGE_RESOLVE_SYMLINKS TRUE)
+set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+set(CMAKE_VISIBILITY_INLINES_HIDDEN ON)
+# On Linux, symbols in executables are not accessible by loaded shared libraries (e.g. via dlopen(3)). However, we need to export public symbols in executables so that extensions can access public symbols. This enables that behaviour.
+set(CMAKE_ENABLE_EXPORTS TRUE)
+
+option(ENABLE_WERROR "Treat all warnings as errors" FALSE)
+if(ENABLE_WERROR)
+ if (CMAKE_VERSION VERSION_GREATER "3.24.0" OR CMAKE_VERSION VERSION_EQUAL "3.24.0")
+ set(CMAKE_COMPILE_WARNING_AS_ERROR TRUE)
+ elseif (MSVC)
+ add_compile_options(\WX)
+ else ()
+ add_compile_options(-Werror)
+ endif()
+endif()
+
+# Detect OS and architecture, copied from DuckDB
+set(OS_NAME "unknown")
+set(OS_ARCH "amd64")
+
+string(REGEX MATCH "(arm64|aarch64)" IS_ARM "${CMAKE_SYSTEM_PROCESSOR}")
+if(IS_ARM)
+ set(OS_ARCH "arm64")
+elseif(FORCE_32_BIT)
+ set(OS_ARCH "i386")
+endif()
+
+if(APPLE)
+ set(OS_NAME "osx")
+endif()
+if(WIN32)
+ set(OS_NAME "windows")
+endif()
+if(UNIX AND NOT APPLE)
+ set(OS_NAME "linux") # sorry BSD
+endif()
+
+if(CMAKE_SIZEOF_VOID_P EQUAL 8)
+ message(STATUS "64-bit architecture detected")
+ add_compile_definitions(__64BIT__)
+elseif(CMAKE_SIZEOF_VOID_P EQUAL 4)
+ message(STATUS "32-bit architecture detected")
+ add_compile_definitions(__32BIT__)
+ set(__32BIT__ TRUE)
+endif()
+
+if(NOT CMAKE_BUILD_TYPE)
+ set(CMAKE_BUILD_TYPE Release)
+endif()
+
+if(DEFINED ENV{PYBIND11_PYTHON_VERSION})
+ set(PYBIND11_PYTHON_VERSION $ENV{PYBIND11_PYTHON_VERSION})
+endif()
+
+if(DEFINED ENV{PYTHON_EXECUTABLE})
+ set(PYTHON_EXECUTABLE $ENV{PYTHON_EXECUTABLE})
+endif()
+
+find_program(CCACHE_PROGRAM ccache)
+if (CCACHE_PROGRAM)
+ set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
+ set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
+ message(STATUS "ccache found and enabled")
+else ()
+ find_program(CCACHE_PROGRAM sccache)
+ if (CCACHE_PROGRAM)
+ set(CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
+ set(CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}")
+ message(STATUS "sccache found and enabled")
+ endif ()
+endif ()
+
+set(INSTALL_LIB_DIR
+ lib
+ CACHE PATH "Installation directory for libraries")
+set(INSTALL_BIN_DIR
+ bin
+ CACHE PATH "Installation directory for executables")
+set(INSTALL_INCLUDE_DIR
+ include
+ CACHE PATH "Installation directory for header files")
+set(INSTALL_CMAKE_DIR
+ ${DEF_INSTALL_CMAKE_DIR}
+ CACHE PATH "Installation directory for CMake files")
+
+option(ENABLE_ADDRESS_SANITIZER "Enable address sanitizer." FALSE)
+option(ENABLE_THREAD_SANITIZER "Enable thread sanitizer." FALSE)
+option(ENABLE_UBSAN "Enable undefined behavior sanitizer." FALSE)
+option(ENABLE_RUNTIME_CHECKS "Enable runtime coherency checks (e.g. asserts)" FALSE)
+option(ENABLE_LTO "Enable Link-Time Optimization" FALSE)
+option(ENABLE_MALLOC_BUFFER_MANAGER "Enable Buffer manager using malloc. Default option for webassembly" OFF)
+
+option(LBUG_DEFAULT_REL_STORAGE_DIRECTION "Only store fwd direction in rel tables by default." BOTH)
+if(NOT LBUG_DEFAULT_REL_STORAGE_DIRECTION)
+ set(LBUG_DEFAULT_REL_STORAGE_DIRECTION BOTH)
+endif()
+set(LBUG_DEFAULT_REL_STORAGE_DIRECTION ${LBUG_DEFAULT_REL_STORAGE_DIRECTION}_REL_STORAGE)
+option(LBUG_PAGE_SIZE_LOG2 "Log2 of the page size." 12)
+if(NOT LBUG_PAGE_SIZE_LOG2)
+ set(LBUG_PAGE_SIZE_LOG2 12)
+endif()
+message(STATUS "LBUG_PAGE_SIZE_LOG2: ${LBUG_PAGE_SIZE_LOG2}")
+option(LBUG_VECTOR_CAPACITY_LOG2 "Log2 of the vector capacity." 11)
+if(NOT LBUG_VECTOR_CAPACITY_LOG2)
+ set(LBUG_VECTOR_CAPACITY_LOG2 11)
+endif()
+message(STATUS "LBUG_VECTOR_CAPACITY_LOG2: ${LBUG_VECTOR_CAPACITY_LOG2}")
+
+# 64 * 2048 nodes per group
+option(LBUG_NODE_GROUP_SIZE_LOG2 "Log2 of the vector capacity." 17)
+if(NOT LBUG_NODE_GROUP_SIZE_LOG2)
+ set(LBUG_NODE_GROUP_SIZE_LOG2 17)
+endif()
+message(STATUS "LBUG_NODE_GROUP_SIZE_LOG2: ${LBUG_NODE_GROUP_SIZE_LOG2}")
+
+option(LBUG_MAX_SEGMENT_SIZE_LOG2 "Log2 of the maximum segment size in bytes." 18)
+if(NOT LBUG_MAX_SEGMENT_SIZE_LOG2)
+ set(LBUG_MAX_SEGMENT_SIZE_LOG2 18)
+endif()
+message(STATUS "LBUG_MAX_SEGMENT_SIZE_LOG2: ${LBUG_MAX_SEGMENT_SIZE_LOG2}")
+
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/system_config.h.in ${CMAKE_CURRENT_BINARY_DIR}/src/include/common/system_config.h @ONLY)
+
+include(CheckCXXSymbolExists)
+check_cxx_symbol_exists(F_FULLFSYNC "fcntl.h" HAS_FULLFSYNC)
+check_cxx_symbol_exists(fdatasync "unistd.h" HAS_FDATASYNC)
+if(HAS_FULLFSYNC)
+ message(STATUS "✓ F_FULLFSYNC will be used on this platform")
+ add_compile_definitions(HAS_FULLFSYNC)
+else()
+ message(STATUS "✗ F_FULLFSYNC not available")
+endif()
+
+if(HAS_FDATASYNC)
+ message(STATUS "✓ fdatasync will be used on this platform")
+ add_compile_definitions(HAS_FDATASYNC)
+else()
+ message(STATUS "✗ fdatasync not available, using fsync fallback")
+endif()
+
+if(MSVC)
+ # Required for M_PI on Windows
+ add_compile_definitions(_USE_MATH_DEFINES)
+ add_compile_definitions(NOMINMAX)
+ add_compile_definitions(SERD_STATIC)
+ # This is a workaround for regex oom issue on windows in gtest.
+ add_compile_definitions(_REGEX_MAX_STACK_COUNT=0)
+ add_compile_definitions(_REGEX_MAX_COMPLEXITY_COUNT=0)
+ # Disable constexpr mutex constructor to avoid compatibility issues with
+ # older versions of the MSVC runtime library
+ # See: https://github.com/microsoft/STL/wiki/Changelog#vs-2022-1710
+ add_compile_definitions(_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR)
+ # TODO (bmwinger): Figure out if this can be set automatically by cmake,
+ # or at least better integrated with user-specified options
+ # For now, hardcode _AMD64_
+ # CMAKE_GENERATOR_PLATFORM can be used for visual studio builds, but not for ninja
+ add_compile_definitions(_AMD64_)
+ # Non-english windows system may use other encodings other than utf-8 (e.g. Chinese use GBK).
+ add_compile_options("/utf-8")
+ # Enables support for custom hardware exception handling
+ add_compile_options("/EHa")
+ # Reduces the size of the static library by roughly 1/2
+ add_compile_options("/Zc:inline")
+ # Disable type conversion warnings
+ add_compile_options(/wd4244 /wd4267)
+ # Remove the default to avoid warnings
+ STRING(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+ STRING(REPLACE "/EHs" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+ # Store all libraries and binaries in the same directory so that lbug_shared.dll is found at runtime
+ set(LIBRARY_OUTPUT_PATH "${CMAKE_BINARY_DIR}/src")
+ set(EXECUTABLE_OUTPUT_PATH "${CMAKE_BINARY_DIR}/src")
+ # This is a workaround for regex stackoverflow issue on windows in gtest.
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /STACK:8388608")
+
+ string(REGEX REPLACE "/W[3|4]" "/w" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+ add_compile_options($<$:/W0>)
+else()
+ add_compile_options(-Wall -Wextra)
+ # Disable warnings for unknown pragmas, which is used by several third-party libraries
+ add_compile_options(-Wno-unknown-pragmas)
+endif()
+
+if(${BUILD_WASM})
+ if(NOT __SINGLE_THREADED__)
+ add_compile_options(-pthread)
+ add_link_options(-pthread)
+ add_link_options(-sPTHREAD_POOL_SIZE=8)
+ endif()
+ add_compile_options(-s DISABLE_EXCEPTION_CATCHING=0)
+ add_link_options(-sSTACK_SIZE=4MB)
+ add_link_options(-sASSERTIONS=1)
+ add_link_options(-lembind)
+ add_link_options(-sWASM_BIGINT)
+
+ if(BUILD_TESTS OR BUILD_EXTENSION_TESTS)
+ add_link_options(-sINITIAL_MEMORY=3892MB)
+ add_link_options(-sNODERAWFS=1)
+ elseif(WASM_NODEFS)
+ add_link_options(-sNODERAWFS=1)
+ add_link_options(-sALLOW_MEMORY_GROWTH=1)
+ add_link_options(-sMODULARIZE=1)
+ add_link_options(-sEXPORTED_RUNTIME_METHODS=FS,wasmMemory)
+ add_link_options(-sEXPORT_NAME=lbug)
+ add_link_options(-sMAXIMUM_MEMORY=4GB)
+ else()
+ add_link_options(-sSINGLE_FILE=1)
+ add_link_options(-sALLOW_MEMORY_GROWTH=1)
+ add_link_options(-sMODULARIZE=1)
+ add_link_options(-sEXPORTED_RUNTIME_METHODS=FS,wasmMemory)
+ add_link_options(-lidbfs.js)
+ add_link_options(-lworkerfs.js)
+ add_link_options(-sEXPORT_NAME=lbug)
+ add_link_options(-sMAXIMUM_MEMORY=4GB)
+ endif()
+ set(__WASM__ TRUE)
+ add_compile_options(-fexceptions)
+ add_link_options(-s DISABLE_EXCEPTION_CATCHING=0)
+ add_link_options(-fexceptions)
+ add_compile_definitions(__WASM__)
+ set(ENABLE_MALLOC_BUFFER_MANAGER ON)
+endif()
+
+if(${BUILD_SWIFT})
+ add_compile_definitions(__SWIFT__)
+ set(ENABLE_MALLOC_BUFFER_MANAGER ON)
+endif()
+
+if (${ENABLE_MALLOC_BUFFER_MANAGER})
+ add_compile_definitions(BM_MALLOC)
+endif()
+
+if(ANDROID_ABI)
+ message(STATUS "Android ABI detected: ${ANDROID_ABI}")
+ add_compile_definitions(__ANDROID__)
+ set(__ANDROID__ TRUE)
+endif()
+
+if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+ add_compile_options(-Wno-restrict) # no restrict until https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105651 is fixed
+endif()
+
+if(${ENABLE_THREAD_SANITIZER} AND (NOT __SINGLE_THREADED__))
+ if(MSVC)
+ message(FATAL_ERROR "Thread sanitizer is not supported on MSVC")
+ else()
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread -fno-omit-frame-pointer")
+ endif()
+endif()
+if(${ENABLE_ADDRESS_SANITIZER})
+ if(MSVC)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fsanitize=address")
+ else()
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
+ endif()
+endif()
+if(${ENABLE_UBSAN})
+ if(MSVC)
+ message(FATAL_ERROR "Undefined behavior sanitizer is not supported on MSVC")
+ else()
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fno-omit-frame-pointer")
+ endif()
+endif()
+
+if(${ENABLE_RUNTIME_CHECKS})
+ add_compile_definitions(LBUG_RUNTIME_CHECKS)
+endif()
+
+if (${ENABLE_DESER_DEBUG})
+ add_compile_definitions(LBUG_DESER_DEBUG)
+endif()
+
+if(${ENABLE_LTO})
+ set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
+endif()
+
+option(AUTO_UPDATE_GRAMMAR "Automatically regenerate C++ grammar files on change." TRUE)
+option(BUILD_BENCHMARK "Build benchmarks." FALSE)
+option(BUILD_EXTENSIONS "Semicolon-separated list of extensions to build." "")
+option(BUILD_EXAMPLES "Build examples." FALSE)
+option(BUILD_JAVA "Build Java API." FALSE)
+option(BUILD_NODEJS "Build NodeJS API." FALSE)
+option(BUILD_PYTHON "Build Python API." FALSE)
+option(BUILD_SHELL "Build Interactive Shell" TRUE)
+option(BUILD_SINGLE_FILE_HEADER "Build single file header. Requires Python >= 3.9." TRUE)
+option(BUILD_TESTS "Build C++ tests." FALSE)
+option(BUILD_EXTENSION_TESTS "Build C++ extension tests." FALSE)
+option(BUILD_LBUG "Build Lbug." TRUE)
+option(ENABLE_BACKTRACES "Enable backtrace printing for exceptions and segfaults" FALSE)
+option(USE_STD_FORMAT "Use std::format instead of a custom formatter." FALSE)
+option(PREFER_SYSTEM_DEPS "Only download certain deps if not found on the system" TRUE)
+
+option(BUILD_LCOV "Build coverage report." FALSE)
+if(${BUILD_LCOV})
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage")
+endif()
+
+if (ENABLE_BACKTRACES)
+ set(DOWNLOAD_CPPTRACE TRUE)
+ if(${PREFER_SYSTEM_DEPS})
+ find_package(cpptrace QUIET)
+ if(cpptrace_FOUND)
+ message(STATUS "Using system cpptrace")
+ set(DOWNLOAD_CPPTRACE FALSE)
+ endif()
+ endif()
+ if(${DOWNLOAD_CPPTRACE})
+ message(STATUS "Fetching cpptrace from GitHub...")
+ include(FetchContent)
+ FetchContent_Declare(
+ cpptrace
+ GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git
+ GIT_TAG v0.8.3
+ GIT_SHALLOW TRUE
+ )
+ FetchContent_MakeAvailable(cpptrace)
+ endif()
+ add_compile_definitions(LBUG_BACKTRACE)
+endif()
+
+if (USE_STD_FORMAT)
+ add_compile_definitions(USE_STD_FORMAT)
+endif()
+
+function(add_lbug_test TEST_NAME)
+ set(SRCS ${ARGN})
+ add_executable(${TEST_NAME} ${SRCS})
+ target_link_libraries(${TEST_NAME} PRIVATE test_helper test_runner graph_test)
+ if (ENABLE_BACKTRACES)
+ target_link_libraries(${TEST_NAME} PRIVATE register_backtrace_signal_handler)
+ endif()
+ target_include_directories(${TEST_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/test/include)
+ include(GoogleTest)
+
+ if (TEST_NAME STREQUAL "e2e_test")
+ gtest_discover_tests(${TEST_NAME}
+ DISCOVERY_TIMEOUT 600
+ DISCOVERY_MODE PRE_TEST
+ TEST_PREFIX e2e_test_
+ )
+ else()
+ gtest_discover_tests(${TEST_NAME}
+ DISCOVERY_TIMEOUT 600
+ DISCOVERY_MODE PRE_TEST
+ )
+ endif()
+endfunction()
+
+function(add_lbug_api_test TEST_NAME)
+ set(SRCS ${ARGN})
+ add_executable(${TEST_NAME} ${SRCS})
+ target_link_libraries(${TEST_NAME} PRIVATE api_graph_test api_test_helper)
+ if (ENABLE_BACKTRACES)
+ target_link_libraries(${TEST_NAME} PRIVATE register_backtrace_signal_handler)
+ endif()
+ target_include_directories(${TEST_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/test/include)
+ include(GoogleTest)
+ gtest_discover_tests(${TEST_NAME})
+endfunction()
+
+# Windows doesn't support dynamic lookup, so we have to link extensions against lbug.
+if (MSVC AND (NOT BUILD_EXTENSIONS EQUAL ""))
+ set(BUILD_LBUG TRUE)
+endif ()
+
+include_directories(third_party/antlr4_cypher/include)
+include_directories(third_party/antlr4_runtime/src)
+include_directories(third_party/brotli/c/include)
+include_directories(third_party/fast_float/include)
+include_directories(third_party/mbedtls/include)
+include_directories(third_party/parquet)
+include_directories(third_party/snappy)
+include_directories(third_party/thrift)
+include_directories(third_party/miniz)
+include_directories(third_party/nlohmann_json)
+include_directories(third_party/pybind11/include)
+include_directories(third_party/pyparse)
+include_directories(third_party/re2/include)
+include_directories(third_party/alp/include)
+if (${BUILD_TESTS} OR ${BUILD_EXTENSION_TESTS})
+ include_directories(third_party/spdlog)
+elseif (${BUILD_BENCHMARK})
+ include_directories(third_party/spdlog)
+endif ()
+include_directories(third_party/utf8proc/include)
+include_directories(third_party/zstd/include)
+include_directories(third_party/httplib)
+include_directories(third_party/pcg)
+include_directories(third_party/lz4)
+include_directories(third_party/roaring_bitmap)
+# Use SYSTEM to suppress warnings from simsimd
+include_directories(SYSTEM third_party/simsimd/include)
+
+add_subdirectory(third_party)
+
+add_definitions(-DLBUG_ROOT_DIRECTORY="${PROJECT_SOURCE_DIR}")
+add_definitions(-DLBUG_CMAKE_VERSION="${CMAKE_PROJECT_VERSION}")
+add_definitions(-DLBUG_EXTENSION_VERSION="0.12.0")
+
+if(BUILD_LBUG)
+include_directories(
+ src/include
+ ${CMAKE_CURRENT_BINARY_DIR}/src/include
+)
+endif()
+
+if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/extension/CMakeLists.txt")
+ add_subdirectory(extension)
+endif ()
+
+if(BUILD_LBUG)
+
+add_subdirectory(src)
+
+# Link extensions which require static linking.
+foreach(ext IN LISTS STATICALLY_LINKED_EXTENSIONS)
+ if (${BUILD_EXTENSION_TESTS})
+ add_compile_definitions(__STATIC_LINK_EXTENSION_TEST__)
+ endif ()
+ target_link_libraries(lbug PRIVATE "lbug_${ext}_static_extension")
+ target_link_libraries(lbug_shared PRIVATE "lbug_${ext}_static_extension")
+endforeach()
+
+if (${BUILD_TESTS} OR ${BUILD_EXTENSION_TESTS})
+ add_subdirectory(test)
+elseif (${BUILD_BENCHMARK})
+ add_subdirectory(test/test_helper)
+endif ()
+add_subdirectory(tools)
+endif ()
+
+if (${BUILD_EXAMPLES})
+ add_subdirectory(examples/c)
+ add_subdirectory(examples/cpp)
+endif()
diff --git a/graph-wasm/lbug-0.12.2/lbug-src/README.md b/graph-wasm/lbug-0.12.2/lbug-src/README.md
new file mode 100644
index 0000000000..8ffe98d7c9
--- /dev/null
+++ b/graph-wasm/lbug-0.12.2/lbug-src/README.md
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# Ladybug
+Ladybug is an embedded graph database built for query speed and scalability. Ladybug is optimized for handling complex analytical workloads
+on very large databases and provides a set of retrieval features, such as a full text search and vector indices. Our core feature set includes:
+
+- Flexible Property Graph Data Model and Cypher query language
+- Embeddable, serverless integration into applications
+- Native full text search and vector index
+- Columnar disk-based storage
+- Columnar sparse row-based (CSR) adjacency list/join indices
+- Vectorized and factorized query processor
+- Novel and very fast join algorithms
+- Multi-core query parallelism
+- Serializable ACID transactions
+- Wasm (WebAssembly) bindings for fast, secure execution in the browser
+
+Ladybug is being developed by [LadybugDB Developers](https://github.com/LadybugDB) and
+is available under a permissible license. So try it out and help us make it better! We welcome your feedback and feature requests.
+
+The database was formerly known as [Kuzu](https://github.com/kuzudb/kuzu).
+
+## Installation
+
+> [!WARNING]
+> Many of these binary installation methods are not functional yet. We need to work through package names, availability and convention issues.
+> For now, use the build from source method.
+
+| Language | Installation |
+| -------- |------------------------------------------------------------------------|
+| Python | `pip install real_ladybug` |
+| NodeJS | `npm install lbug` |
+| Rust | `cargo add lbug` |
+| Go | `go get github.com/lbugdb/go-lbug` |
+| Swift | [lbug-swift](https://github.com/lbugdb/lbug-swift) |
+| Java | [Maven Central](https://central.sonatype.com/artifact/com.ladybugdb/lbug) |
+| C/C++ | [precompiled binaries](https://github.com/LadybugDB/ladybug/releases/latest) |
+| CLI | [precompiled binaries](https://github.com/LadybugDB/ladybug/releases/latest) |
+
+To learn more about installation, see our [Installation](https://docs.ladybugdb.com/installation) page.
+
+## Getting Started
+
+Refer to our [Getting Started](https://docs.ladybugdb.com/get-started/) page for your first example.
+
+## Build from Source
+
+You can build from source using the instructions provided in the [developer guide](https://docs.ladybugdb.com/developer-guide/).
+
+## Contributing
+We welcome contributions to Ladybug. If you are interested in contributing to Ladybug, please read our [Contributing Guide](CONTRIBUTING.md).
+
+## License
+By contributing to Ladybug, you agree that your contributions will be licensed under the [MIT License](LICENSE).
+
+## Contact
+You can contact us at [social@ladybugdb.com](mailto:social@ladybugdb.com) or [join our Discord community](https://discord.com/invite/hXyHmvW3Vy).
diff --git a/graph-wasm/lbug-0.12.2/lbug-src/cmake/templates/system_config.h.in b/graph-wasm/lbug-0.12.2/lbug-src/cmake/templates/system_config.h.in
new file mode 100644
index 0000000000..971c5874c2
--- /dev/null
+++ b/graph-wasm/lbug-0.12.2/lbug-src/cmake/templates/system_config.h.in
@@ -0,0 +1,75 @@
+/*
+ * This is a template header used for generating the header 'system_config.h'
+ * Any value in the format @VALUE_NAME@ can be substituted with a value passed into CMakeLists.txt
+ * See https://cmake.org/cmake/help/latest/command/configure_file.html for more details
+ */
+
+#pragma once
+
+#include
+#include
+
+#include "common/enums/extend_direction.h"
+
+#define BOTH_REL_STORAGE 0
+#define FWD_REL_STORAGE 1
+#define BWD_REL_STORAGE 2
+
+namespace lbug {
+namespace common {
+
+#define VECTOR_CAPACITY_LOG_2 @LBUG_VECTOR_CAPACITY_LOG2@
+#if VECTOR_CAPACITY_LOG_2 > 12
+#error "Vector capacity log2 should be less than or equal to 12"
+#endif
+constexpr uint64_t DEFAULT_VECTOR_CAPACITY = static_cast(1) << VECTOR_CAPACITY_LOG_2;
+
+// Currently the system supports files with 2 different pages size, which we refer to as
+// PAGE_SIZE and TEMP_PAGE_SIZE. PAGE_SIZE is the default size of the page which is the
+// unit of read/write to the database files.
+static constexpr uint64_t PAGE_SIZE_LOG2 = @LBUG_PAGE_SIZE_LOG2@; // Default to 4KB.
+static constexpr uint64_t LBUG_PAGE_SIZE = static_cast(1) << PAGE_SIZE_LOG2;
+// Page size for files with large pages, e.g., temporary files that are used by operators that
+// may require large amounts of memory.
+static constexpr uint64_t TEMP_PAGE_SIZE_LOG2 = 18;
+static const uint64_t TEMP_PAGE_SIZE = static_cast(1) << TEMP_PAGE_SIZE_LOG2;
+
+#define DEFAULT_REL_STORAGE_DIRECTION @LBUG_DEFAULT_REL_STORAGE_DIRECTION@
+#if DEFAULT_REL_STORAGE_DIRECTION == FWD_REL_STORAGE
+static constexpr ExtendDirection DEFAULT_EXTEND_DIRECTION = ExtendDirection::FWD;
+#elif DEFAULT_REL_STORAGE_DIRECTION == BWD_REL_STORAGE
+static constexpr ExtendDirection DEFAULT_EXTEND_DIRECTION = ExtendDirection::BWD;
+#else
+static constexpr ExtendDirection DEFAULT_EXTEND_DIRECTION = ExtendDirection::BOTH;
+#endif
+
+struct StorageConfig {
+ static constexpr uint64_t NODE_GROUP_SIZE_LOG2 = @LBUG_NODE_GROUP_SIZE_LOG2@;
+ static constexpr uint64_t NODE_GROUP_SIZE = static_cast(1) << NODE_GROUP_SIZE_LOG2;
+ // The number of CSR lists in a leaf region.
+ static constexpr uint64_t CSR_LEAF_REGION_SIZE_LOG2 =
+ std::min(static_cast(10), NODE_GROUP_SIZE_LOG2 - 1);
+ static constexpr uint64_t CSR_LEAF_REGION_SIZE = static_cast(1)
+ << CSR_LEAF_REGION_SIZE_LOG2;
+ static constexpr uint64_t CHUNKED_NODE_GROUP_CAPACITY =
+ std::min(static_cast(2048), NODE_GROUP_SIZE);
+
+ // Maximum size for a segment in bytes
+ static constexpr uint64_t MAX_SEGMENT_SIZE_LOG2 = @LBUG_MAX_SEGMENT_SIZE_LOG2@;
+ static constexpr uint64_t MAX_SEGMENT_SIZE = 1 << MAX_SEGMENT_SIZE_LOG2;
+};
+
+struct OrderByConfig {
+ static constexpr uint64_t MIN_SIZE_TO_REDUCE = common::DEFAULT_VECTOR_CAPACITY * 5;
+};
+
+struct CopyConfig {
+ static constexpr uint64_t PANDAS_PARTITION_COUNT = 50 * DEFAULT_VECTOR_CAPACITY;
+};
+
+} // namespace common
+} // namespace lbug
+
+#undef BOTH_REL_STORAGE
+#undef FWD_REL_STORAGE
+#undef BWD_REL_STORAGE
diff --git a/graph-wasm/lbug-0.12.2/lbug-src/src/CMakeLists.txt b/graph-wasm/lbug-0.12.2/lbug-src/src/CMakeLists.txt
new file mode 100644
index 0000000000..2250ce477f
--- /dev/null
+++ b/graph-wasm/lbug-0.12.2/lbug-src/src/CMakeLists.txt
@@ -0,0 +1,79 @@
+include_directories(${CMAKE_CURRENT_BINARY_DIR})
+
+# Have to pass this down to every subdirectory, which actually adds the files.
+# This doesn't affect parent directories.
+add_compile_definitions(LBUG_EXPORTS)
+add_compile_definitions(ANTLR4CPP_STATIC)
+add_subdirectory(binder)
+add_subdirectory(c_api)
+add_subdirectory(catalog)
+add_subdirectory(common)
+add_subdirectory(expression_evaluator)
+add_subdirectory(function)
+add_subdirectory(graph)
+add_subdirectory(main)
+add_subdirectory(optimizer)
+add_subdirectory(parser)
+add_subdirectory(planner)
+add_subdirectory(processor)
+add_subdirectory(storage)
+add_subdirectory(transaction)
+add_subdirectory(extension)
+
+add_library(lbug STATIC ${ALL_OBJECT_FILES})
+add_library(lbug_shared SHARED ${ALL_OBJECT_FILES})
+
+set(LBUG_LIBRARIES antlr4_cypher antlr4_runtime brotlidec brotlicommon fast_float utf8proc re2 fastpfor parquet snappy thrift yyjson zstd miniz mbedtls lz4 roaring_bitmap simsimd)
+if (NOT __SINGLE_THREADED__)
+ set(LBUG_LIBRARIES ${LBUG_LIBRARIES} Threads::Threads)
+endif()
+if(NOT WIN32)
+ set(LBUG_LIBRARIES dl ${LBUG_LIBRARIES})
+endif()
+# Seems to be needed for clang on linux only
+# for compiling std::atomic::compare_exchange_weak
+if ((NOT APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang") AND NOT __WASM__ AND NOT __SINGLE_THREADED__)
+ set(LBUG_LIBRARIES atomic ${LBUG_LIBRARIES})
+endif()
+if (ENABLE_BACKTRACES)
+ set(LBUG_LIBRARIES ${LBUG_LIBRARIES} cpptrace::cpptrace)
+endif()
+target_link_libraries(lbug PUBLIC ${LBUG_LIBRARIES})
+target_link_libraries(lbug_shared PUBLIC ${LBUG_LIBRARIES})
+unset(LBUG_LIBRARIES)
+
+set(LBUG_INCLUDES $ $ ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/include/c_api ${CMAKE_CURRENT_BINARY_DIR}/../src/include)
+target_include_directories(lbug PUBLIC ${LBUG_INCLUDES})
+target_include_directories(lbug_shared PUBLIC ${LBUG_INCLUDES})
+unset(LBUG_INCLUDES)
+
+if(WIN32)
+ # Anything linking against the static library must not use dllimport.
+ target_compile_definitions(lbug INTERFACE LBUG_STATIC_DEFINE)
+endif()
+
+if(NOT WIN32)
+ set_target_properties(lbug_shared PROPERTIES OUTPUT_NAME lbug)
+endif()
+
+install(TARGETS lbug lbug_shared)
+
+if(${BUILD_SINGLE_FILE_HEADER})
+ # Create a command to generate lbug.hpp, and then create a target that is
+ # always built that depends on it. This allows our generator to detect when
+ # exactly to build lbug.hpp, while still building the target by default.
+ find_package(Python3 3.9...4 REQUIRED)
+ add_custom_command(
+ OUTPUT lbug.hpp
+ COMMAND
+ ${Python3_EXECUTABLE} ${PROJECT_SOURCE_DIR}/scripts/collect-single-file-header.py ${CMAKE_CURRENT_BINARY_DIR}/..
+ DEPENDS
+ ${PROJECT_SOURCE_DIR}/scripts/collect-single-file-header.py lbug_shared)
+ add_custom_target(single_file_header ALL DEPENDS lbug.hpp)
+endif()
+
+install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/c_api/lbug.h TYPE INCLUDE)
+
+if(${BUILD_SINGLE_FILE_HEADER})
+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/lbug.hpp TYPE INCLUDE)
+endif()
diff --git a/graph-wasm/lbug-0.12.2/lbug-src/src/antlr4/Cypher.g4 b/graph-wasm/lbug-0.12.2/lbug-src/src/antlr4/Cypher.g4
new file mode 100644
index 0000000000..e409515c72
--- /dev/null
+++ b/graph-wasm/lbug-0.12.2/lbug-src/src/antlr4/Cypher.g4
@@ -0,0 +1,917 @@
+
+ku_Statements
+ : oC_Cypher ( SP? ';' SP? oC_Cypher )* SP? EOF ;
+
+oC_Cypher
+ : oC_AnyCypherOption? SP? ( oC_Statement ) ( SP? ';' )?;
+
+oC_Statement
+ : oC_Query
+ | kU_CreateUser
+ | kU_CreateRole
+ | kU_CreateNodeTable
+ | kU_CreateRelTable
+ | kU_CreateSequence
+ | kU_CreateType
+ | kU_Drop
+ | kU_AlterTable
+ | kU_CopyFrom
+ | kU_CopyFromByColumn
+ | kU_CopyTO
+ | kU_StandaloneCall
+ | kU_CreateMacro
+ | kU_CommentOn
+ | kU_Transaction
+ | kU_Extension
+ | kU_ExportDatabase
+ | kU_ImportDatabase
+ | kU_AttachDatabase
+ | kU_DetachDatabase
+ | kU_UseDatabase;
+
+kU_CopyFrom
+ : COPY SP oC_SchemaName kU_ColumnNames? SP FROM SP kU_ScanSource ( SP? '(' SP? kU_Options SP? ')' )? ;
+
+kU_ColumnNames
+ : SP? '(' SP? (oC_SchemaName ( SP? ',' SP? oC_SchemaName )* SP?)? ')';
+
+kU_ScanSource
+ : kU_FilePaths
+ | '(' SP? oC_Query SP? ')'
+ | oC_Parameter
+ | oC_Variable
+ | oC_Variable '.' SP? oC_SchemaName
+ | oC_FunctionInvocation ;
+
+kU_CopyFromByColumn
+ : COPY SP oC_SchemaName SP FROM SP '(' SP? StringLiteral ( SP? ',' SP? StringLiteral )* ')' SP BY SP COLUMN ;
+
+kU_CopyTO
+ : COPY SP '(' SP? oC_Query SP? ')' SP TO SP StringLiteral ( SP? '(' SP? kU_Options SP? ')' )? ;
+
+kU_ExportDatabase
+ : EXPORT SP DATABASE SP StringLiteral ( SP? '(' SP? kU_Options SP? ')' )? ;
+
+kU_ImportDatabase
+ : IMPORT SP DATABASE SP StringLiteral;
+
+kU_AttachDatabase
+ : ATTACH SP StringLiteral (SP AS SP oC_SchemaName)? SP '(' SP? DBTYPE SP oC_SymbolicName (SP? ',' SP? kU_Options)? SP? ')' ;
+
+kU_Option
+ : oC_SymbolicName (SP? '=' SP? | SP*) oC_Literal | oC_SymbolicName;
+
+kU_Options
+ : kU_Option ( SP? ',' SP? kU_Option )* ;
+
+kU_DetachDatabase
+ : DETACH SP oC_SchemaName;
+
+kU_UseDatabase
+ : USE SP oC_SchemaName;
+
+kU_StandaloneCall
+ : CALL SP oC_SymbolicName SP? '=' SP? oC_Expression
+ | CALL SP oC_FunctionInvocation;
+
+kU_CommentOn
+ : COMMENT SP ON SP TABLE SP oC_SchemaName SP IS SP StringLiteral ;
+
+kU_CreateMacro
+ : CREATE SP MACRO SP oC_FunctionName SP? '(' SP? kU_PositionalArgs? SP? kU_DefaultArg? ( SP? ',' SP? kU_DefaultArg )* SP? ')' SP AS SP oC_Expression ;
+
+kU_PositionalArgs
+ : oC_SymbolicName ( SP? ',' SP? oC_SymbolicName )* ;
+
+kU_DefaultArg
+ : oC_SymbolicName SP? ':' '=' SP? oC_Literal ;
+
+kU_FilePaths
+ : '[' SP? StringLiteral ( SP? ',' SP? StringLiteral )* ']'
+ | StringLiteral
+ | GLOB SP? '(' SP? StringLiteral SP? ')' ;
+
+kU_IfNotExists
+ : IF SP NOT SP EXISTS ;
+
+kU_CreateNodeTable
+ : CREATE SP NODE SP TABLE SP (kU_IfNotExists SP)? oC_SchemaName ( SP? '(' SP? kU_PropertyDefinitions SP? ( ',' SP? kU_CreateNodeConstraint )? SP? ')' | SP AS SP oC_Query ) ;
+
+kU_CreateRelTable
+ : CREATE SP REL SP TABLE ( SP GROUP )? ( SP kU_IfNotExists )? SP oC_SchemaName
+ SP? '(' SP?
+ kU_FromToConnections SP? (
+ ( ',' SP? kU_PropertyDefinitions SP? )?
+ ( ',' SP? oC_SymbolicName SP? )? // Constraints
+ ')'
+ | ')' SP AS SP oC_Query )
+ ( SP WITH SP? '(' SP? kU_Options SP? ')')? ;
+
+kU_FromToConnections
+ : kU_FromToConnection ( SP? ',' SP? kU_FromToConnection )* ;
+
+kU_FromToConnection
+ : FROM SP oC_SchemaName SP TO SP oC_SchemaName ;
+
+kU_CreateSequence
+ : CREATE SP SEQUENCE SP (kU_IfNotExists SP)? oC_SchemaName (SP kU_SequenceOptions)* ;
+
+kU_CreateType
+ : CREATE SP TYPE SP oC_SchemaName SP AS SP kU_DataType SP? ;
+
+kU_SequenceOptions
+ : kU_IncrementBy
+ | kU_MinValue
+ | kU_MaxValue
+ | kU_StartWith
+ | kU_Cycle;
+
+kU_WithPasswd
+ : SP WITH SP PASSWORD SP StringLiteral ;
+
+kU_CreateUser
+ : CREATE SP USER SP (kU_IfNotExists SP)? oC_Variable kU_WithPasswd? ;
+
+kU_CreateRole
+ : CREATE SP ROLE SP (kU_IfNotExists SP)? oC_Variable ;
+
+kU_IncrementBy : INCREMENT SP ( BY SP )? MINUS? oC_IntegerLiteral ;
+
+kU_MinValue : (NO SP MINVALUE) | (MINVALUE SP MINUS? oC_IntegerLiteral) ;
+
+kU_MaxValue : (NO SP MAXVALUE) | (MAXVALUE SP MINUS? oC_IntegerLiteral) ;
+
+kU_StartWith : START SP ( WITH SP )? MINUS? oC_IntegerLiteral ;
+
+kU_Cycle : (NO SP)? CYCLE ;
+
+kU_IfExists
+ : IF SP EXISTS ;
+
+kU_Drop
+ : DROP SP (TABLE | SEQUENCE | MACRO) SP (kU_IfExists SP)? oC_SchemaName ;
+
+kU_AlterTable
+ : ALTER SP TABLE SP oC_SchemaName SP kU_AlterOptions ;
+
+kU_AlterOptions
+ : kU_AddProperty
+ | kU_DropProperty
+ | kU_RenameTable
+ | kU_RenameProperty
+ | kU_AddFromToConnection
+ | kU_DropFromToConnection;
+
+kU_AddProperty
+ : ADD SP (kU_IfNotExists SP)? oC_PropertyKeyName SP kU_DataType ( SP kU_Default )? ;
+
+kU_Default
+ : DEFAULT SP oC_Expression ;
+
+kU_DropProperty
+ : DROP SP (kU_IfExists SP)? oC_PropertyKeyName ;
+
+kU_RenameTable
+ : RENAME SP TO SP oC_SchemaName ;
+
+kU_RenameProperty
+ : RENAME SP oC_PropertyKeyName SP TO SP oC_PropertyKeyName ;
+
+kU_AddFromToConnection
+ : ADD SP (kU_IfNotExists SP)? kU_FromToConnection ;
+
+kU_DropFromToConnection
+ : DROP SP (kU_IfExists SP)? kU_FromToConnection ;
+
+kU_ColumnDefinitions: kU_ColumnDefinition ( SP? ',' SP? kU_ColumnDefinition )* ;
+
+kU_ColumnDefinition : oC_PropertyKeyName SP kU_DataType ;
+
+kU_PropertyDefinitions : kU_PropertyDefinition ( SP? ',' SP? kU_PropertyDefinition )* ;
+
+kU_PropertyDefinition : kU_ColumnDefinition ( SP kU_Default )? ( SP PRIMARY SP KEY)?;
+
+kU_CreateNodeConstraint : PRIMARY SP KEY SP? '(' SP? oC_PropertyKeyName SP? ')' ;
+
+DECIMAL: ( 'D' | 'd' ) ( 'E' | 'e' ) ( 'C' | 'c' ) ( 'I' | 'i' ) ( 'M' | 'm' ) ( 'A' | 'a' ) ( 'L' | 'l' ) ;
+
+kU_UnionType
+ : UNION SP? '(' SP? kU_ColumnDefinitions SP? ')' ;
+
+kU_StructType
+ : STRUCT SP? '(' SP? kU_ColumnDefinitions SP? ')' ;
+
+kU_MapType
+ : MAP SP? '(' SP? kU_DataType SP? ',' SP? kU_DataType SP? ')' ;
+
+kU_DecimalType
+ : DECIMAL SP? '(' SP? oC_IntegerLiteral SP? ',' SP? oC_IntegerLiteral SP? ')' ;
+
+kU_DataType
+ : oC_SymbolicName
+ | kU_DataType kU_ListIdentifiers
+ | kU_UnionType
+ | kU_StructType
+ | kU_MapType
+ | kU_DecimalType ;
+
+kU_ListIdentifiers : kU_ListIdentifier ( kU_ListIdentifier )* ;
+
+kU_ListIdentifier : '[' oC_IntegerLiteral? ']' ;
+
+oC_AnyCypherOption
+ : oC_Explain
+ | oC_Profile ;
+
+oC_Explain
+ : EXPLAIN (SP LOGICAL)? ;
+
+oC_Profile
+ : PROFILE ;
+
+kU_Transaction
+ : BEGIN SP TRANSACTION
+ | BEGIN SP TRANSACTION SP READ SP ONLY
+ | COMMIT
+ | ROLLBACK
+ | CHECKPOINT;
+
+kU_Extension
+ : kU_LoadExtension
+ | kU_InstallExtension
+ | kU_UninstallExtension
+ | kU_UpdateExtension ;
+
+kU_LoadExtension
+ : LOAD SP (EXTENSION SP)? ( StringLiteral | oC_Variable ) ;
+
+kU_InstallExtension
+ : (FORCE SP)? INSTALL SP oC_Variable (SP FROM SP StringLiteral)?;
+
+kU_UninstallExtension
+ : UNINSTALL SP oC_Variable;
+
+kU_UpdateExtension
+ : UPDATE SP oC_Variable;
+
+oC_Query
+ : oC_RegularQuery ;
+
+oC_RegularQuery
+ : oC_SingleQuery ( SP? oC_Union )*
+ | (oC_Return SP? )+ oC_SingleQuery { notifyReturnNotAtEnd($ctx->start); }
+ ;
+
+oC_Union
+ : ( UNION SP ALL SP? oC_SingleQuery )
+ | ( UNION SP? oC_SingleQuery ) ;
+
+oC_SingleQuery
+ : oC_SinglePartQuery
+ | oC_MultiPartQuery
+ ;
+
+oC_SinglePartQuery
+ : ( oC_ReadingClause SP? )* oC_Return
+ | ( ( oC_ReadingClause SP? )* oC_UpdatingClause ( SP? oC_UpdatingClause )* ( SP? oC_Return )? )
+ ;
+
+oC_MultiPartQuery
+ : ( kU_QueryPart SP? )+ oC_SinglePartQuery;
+
+kU_QueryPart
+ : (oC_ReadingClause SP? )* ( oC_UpdatingClause SP? )* oC_With ;
+
+oC_UpdatingClause
+ : oC_Create
+ | oC_Merge
+ | oC_Set
+ | oC_Delete
+ ;
+
+oC_ReadingClause
+ : oC_Match
+ | oC_Unwind
+ | kU_InQueryCall
+ | kU_LoadFrom
+ ;
+
+kU_LoadFrom
+ : LOAD ( SP WITH SP HEADERS SP? '(' SP? kU_ColumnDefinitions SP? ')' )? SP FROM SP kU_ScanSource (SP? '(' SP? kU_Options SP? ')')? (SP? oC_Where)? ;
+
+
+oC_YieldItem
+ : ( oC_Variable SP AS SP )? oC_Variable ;
+
+oC_YieldItems
+ : oC_YieldItem ( SP? ',' SP? oC_YieldItem )* ;
+
+kU_InQueryCall
+ : CALL SP oC_FunctionInvocation (SP? oC_Where)? ( SP? YIELD SP oC_YieldItems )? ;
+
+oC_Match
+ : ( OPTIONAL SP )? MATCH SP? oC_Pattern ( SP oC_Where )? ( SP kU_Hint )? ;
+
+kU_Hint
+ : HINT SP kU_JoinNode;
+
+kU_JoinNode
+ : kU_JoinNode SP JOIN SP kU_JoinNode
+ | kU_JoinNode ( SP MULTI_JOIN SP oC_SchemaName)+
+ | '(' SP? kU_JoinNode SP? ')'
+ | oC_SchemaName ;
+
+oC_Unwind : UNWIND SP? oC_Expression SP AS SP oC_Variable ;
+
+oC_Create
+ : CREATE SP? oC_Pattern ;
+
+// For unknown reason, openCypher use oC_PatternPart instead of oC_Pattern. There should be no difference in terms of planning.
+// So we choose to be consistent with oC_Create and use oC_Pattern instead.
+oC_Merge : MERGE SP? oC_Pattern ( SP oC_MergeAction )* ;
+
+oC_MergeAction
+ : ( ON SP MATCH SP oC_Set )
+ | ( ON SP CREATE SP oC_Set )
+ ;
+
+oC_Set
+ : SET SP? oC_SetItem ( SP? ',' SP? oC_SetItem )*
+ | SET SP? oC_Atom SP? '=' SP? kU_Properties;
+
+oC_SetItem
+ : ( oC_PropertyExpression SP? '=' SP? oC_Expression ) ;
+
+oC_Delete
+ : ( DETACH SP )? DELETE SP? oC_Expression ( SP? ',' SP? oC_Expression )*;
+
+oC_With
+ : WITH oC_ProjectionBody ( SP? oC_Where )? ;
+
+oC_Return
+ : RETURN oC_ProjectionBody ;
+
+oC_ProjectionBody
+ : ( SP? DISTINCT )? SP oC_ProjectionItems (SP oC_Order )? ( SP oC_Skip )? ( SP oC_Limit )? ;
+
+oC_ProjectionItems
+ : ( STAR ( SP? ',' SP? oC_ProjectionItem )* )
+ | ( oC_ProjectionItem ( SP? ',' SP? oC_ProjectionItem )* )
+ ;
+
+STAR : '*' ;
+
+oC_ProjectionItem
+ : ( oC_Expression SP AS SP oC_Variable )
+ | oC_Expression
+ ;
+
+oC_Order
+ : ORDER SP BY SP oC_SortItem ( ',' SP? oC_SortItem )* ;
+
+oC_Skip
+ : L_SKIP SP oC_Expression ;
+
+L_SKIP : ( 'S' | 's' ) ( 'K' | 'k' ) ( 'I' | 'i' ) ( 'P' | 'p' ) ;
+
+oC_Limit
+ : LIMIT SP oC_Expression ;
+
+oC_SortItem
+ : oC_Expression ( SP? ( ASCENDING | ASC | DESCENDING | DESC ) )? ;
+
+oC_Where
+ : WHERE SP oC_Expression ;
+
+oC_Pattern
+ : oC_PatternPart ( SP? ',' SP? oC_PatternPart )* ;
+
+oC_PatternPart
+ : ( oC_Variable SP? '=' SP? oC_AnonymousPatternPart )
+ | oC_AnonymousPatternPart ;
+
+oC_AnonymousPatternPart
+ : oC_PatternElement ;
+
+oC_PatternElement
+ : ( oC_NodePattern ( SP? oC_PatternElementChain )* )
+ | ( '(' oC_PatternElement ')' )
+ ;
+
+oC_NodePattern
+ : '(' SP? ( oC_Variable SP? )? ( oC_NodeLabels SP? )? ( kU_Properties SP? )? ')' ;
+
+oC_PatternElementChain
+ : oC_RelationshipPattern SP? oC_NodePattern ;
+
+oC_RelationshipPattern
+ : ( oC_LeftArrowHead SP? oC_Dash SP? oC_RelationshipDetail? SP? oC_Dash )
+ | ( oC_Dash SP? oC_RelationshipDetail? SP? oC_Dash SP? oC_RightArrowHead )
+ | ( oC_Dash SP? oC_RelationshipDetail? SP? oC_Dash )
+ ;
+
+oC_RelationshipDetail
+ : '[' SP? ( oC_Variable SP? )? ( oC_RelationshipTypes SP? )? ( kU_RecursiveDetail SP? )? ( kU_Properties SP? )? ']' ;
+
+// The original oC_Properties definition is oC_MapLiteral | oC_Parameter.
+// We choose to not support parameter as properties which will be the decision for a long time.
+// We then substitute with oC_MapLiteral definition. We create oC_MapLiteral only when we decide to add MAP type.
+kU_Properties
+ : '{' SP? ( oC_PropertyKeyName SP? ':' SP? oC_Expression SP? ( ',' SP? oC_PropertyKeyName SP? ':' SP? oC_Expression SP? )* )? '}';
+
+oC_RelationshipTypes
+ : ':' SP? oC_RelTypeName ( SP? '|' ':'? SP? oC_RelTypeName )* ;
+
+oC_NodeLabels
+ : ':' SP? oC_LabelName ( SP? ('|' ':'? | ':') SP? oC_LabelName )* ;
+
+kU_RecursiveDetail
+ : '*' ( SP? kU_RecursiveType)? ( SP? oC_RangeLiteral )? ( SP? kU_RecursiveComprehension )? ;
+
+kU_RecursiveType
+ : (ALL SP)? WSHORTEST SP? '(' SP? oC_PropertyKeyName SP? ')'
+ | SHORTEST
+ | ALL SP SHORTEST
+ | TRAIL
+ | ACYCLIC ;
+
+oC_RangeLiteral
+ : oC_LowerBound? SP? DOTDOT SP? oC_UpperBound?
+ | oC_IntegerLiteral ;
+
+kU_RecursiveComprehension
+ : '(' SP? oC_Variable SP? ',' SP? oC_Variable ( SP? '|' SP? oC_Where SP? )? ( SP? '|' SP? kU_RecursiveProjectionItems SP? ',' SP? kU_RecursiveProjectionItems SP? )? ')' ;
+
+kU_RecursiveProjectionItems
+ : '{' SP? oC_ProjectionItems? SP? '}' ;
+
+oC_LowerBound
+ : DecimalInteger ;
+
+oC_UpperBound
+ : DecimalInteger ;
+
+oC_LabelName
+ : oC_SchemaName ;
+
+oC_RelTypeName
+ : oC_SchemaName ;
+
+oC_Expression
+ : oC_OrExpression ;
+
+oC_OrExpression
+ : oC_XorExpression ( SP OR SP oC_XorExpression )* ;
+
+oC_XorExpression
+ : oC_AndExpression ( SP XOR SP oC_AndExpression )* ;
+
+oC_AndExpression
+ : oC_NotExpression ( SP AND SP oC_NotExpression )* ;
+
+oC_NotExpression
+ : ( NOT SP? )* oC_ComparisonExpression;
+
+oC_ComparisonExpression
+ : kU_BitwiseOrOperatorExpression ( SP? kU_ComparisonOperator SP? kU_BitwiseOrOperatorExpression )?
+ | kU_BitwiseOrOperatorExpression ( SP? INVALID_NOT_EQUAL SP? kU_BitwiseOrOperatorExpression ) { notifyInvalidNotEqualOperator($INVALID_NOT_EQUAL); }
+ | kU_BitwiseOrOperatorExpression SP? kU_ComparisonOperator SP? kU_BitwiseOrOperatorExpression ( SP? kU_ComparisonOperator SP? kU_BitwiseOrOperatorExpression )+ { notifyNonBinaryComparison($ctx->start); }
+ ;
+
+kU_ComparisonOperator : '=' | '<>' | '<' | '<=' | '>' | '>=' ;
+
+INVALID_NOT_EQUAL : '!=' ;
+
+kU_BitwiseOrOperatorExpression
+ : kU_BitwiseAndOperatorExpression ( SP? '|' SP? kU_BitwiseAndOperatorExpression )* ;
+
+kU_BitwiseAndOperatorExpression
+ : kU_BitShiftOperatorExpression ( SP? '&' SP? kU_BitShiftOperatorExpression )* ;
+
+kU_BitShiftOperatorExpression
+ : oC_AddOrSubtractExpression ( SP? kU_BitShiftOperator SP? oC_AddOrSubtractExpression )* ;
+
+kU_BitShiftOperator : '>>' | '<<' ;
+
+oC_AddOrSubtractExpression
+ : oC_MultiplyDivideModuloExpression ( SP? kU_AddOrSubtractOperator SP? oC_MultiplyDivideModuloExpression )* ;
+
+kU_AddOrSubtractOperator : '+' | '-' ;
+
+oC_MultiplyDivideModuloExpression
+ : oC_PowerOfExpression ( SP? kU_MultiplyDivideModuloOperator SP? oC_PowerOfExpression )* ;
+
+kU_MultiplyDivideModuloOperator : '*' | '/' | '%' ;
+
+oC_PowerOfExpression
+ : oC_StringListNullOperatorExpression ( SP? '^' SP? oC_StringListNullOperatorExpression )* ;
+
+oC_StringListNullOperatorExpression
+ : oC_UnaryAddSubtractOrFactorialExpression ( oC_StringOperatorExpression | oC_ListOperatorExpression+ | oC_NullOperatorExpression )? ;
+
+oC_ListOperatorExpression
+ : ( SP IN SP? oC_PropertyOrLabelsExpression )
+ | ( '[' oC_Expression ']' )
+ | ( '[' oC_Expression? ( COLON | DOTDOT ) oC_Expression? ']' ) ;
+
+COLON : ':' ;
+
+DOTDOT : '..' ;
+
+oC_StringOperatorExpression
+ : ( oC_RegularExpression | ( SP STARTS SP WITH ) | ( SP ENDS SP WITH ) | ( SP CONTAINS ) ) SP? oC_PropertyOrLabelsExpression ;
+
+oC_RegularExpression
+ : SP? '=~' ;
+
+oC_NullOperatorExpression
+ : ( SP IS SP NULL )
+ | ( SP IS SP NOT SP NULL ) ;
+
+MINUS : '-' ;
+
+FACTORIAL : '!' ;
+
+oC_UnaryAddSubtractOrFactorialExpression
+ : ( MINUS SP? )* oC_PropertyOrLabelsExpression (SP? FACTORIAL)? ;
+
+oC_PropertyOrLabelsExpression
+ : oC_Atom ( SP? oC_PropertyLookup )* ;
+
+oC_Atom
+ : oC_Literal
+ | oC_Parameter
+ | oC_CaseExpression
+ | oC_ParenthesizedExpression
+ | oC_FunctionInvocation
+ | oC_PathPatterns
+ | oC_ExistCountSubquery
+ | oC_Variable
+ | oC_Quantifier
+ ;
+
+oC_Quantifier
+ : ( ALL SP? '(' SP? oC_FilterExpression SP? ')' )
+ | ( ANY SP? '(' SP? oC_FilterExpression SP? ')' )
+ | ( NONE SP? '(' SP? oC_FilterExpression SP? ')' )
+ | ( SINGLE SP? '(' SP? oC_FilterExpression SP? ')' )
+ ;
+
+oC_FilterExpression
+ : oC_IdInColl SP oC_Where ;
+
+oC_IdInColl
+ : oC_Variable SP IN SP oC_Expression ;
+
+oC_Literal
+ : oC_NumberLiteral
+ | StringLiteral
+ | oC_BooleanLiteral
+ | NULL
+ | oC_ListLiteral
+ | kU_StructLiteral
+ ;
+
+oC_BooleanLiteral
+ : TRUE
+ | FALSE
+ ;
+
+oC_ListLiteral
+ : '[' SP? ( oC_Expression SP? ( kU_ListEntry SP? )* )? ']' ;
+
+kU_ListEntry
+ : ',' SP? oC_Expression? ;
+
+kU_StructLiteral
+ : '{' SP? kU_StructField SP? ( ',' SP? kU_StructField SP? )* '}' ;
+
+kU_StructField
+ : ( oC_SymbolicName | StringLiteral ) SP? ':' SP? oC_Expression ;
+
+oC_ParenthesizedExpression
+ : '(' SP? oC_Expression SP? ')' ;
+
+oC_FunctionInvocation
+ : COUNT SP? '(' SP? '*' SP? ')'
+ | CAST SP? '(' SP? kU_FunctionParameter SP? ( ( AS SP? kU_DataType ) | ( ',' SP? kU_FunctionParameter ) ) SP? ')'
+ | oC_FunctionName SP? '(' SP? ( DISTINCT SP? )? ( kU_FunctionParameter SP? ( ',' SP? kU_FunctionParameter SP? )* )? ')' ;
+
+oC_FunctionName
+ : oC_SymbolicName ;
+
+kU_FunctionParameter
+ : ( oC_SymbolicName SP? ':' '=' SP? )? oC_Expression
+ | kU_LambdaParameter ;
+
+kU_LambdaParameter
+ : kU_LambdaVars SP? '-' '>' SP? oC_Expression SP? ;
+
+kU_LambdaVars
+ : oC_SymbolicName
+ | '(' SP? oC_SymbolicName SP? ( ',' SP? oC_SymbolicName SP?)* ')' ;
+
+oC_PathPatterns
+ : oC_NodePattern ( SP? oC_PatternElementChain )+;
+
+oC_ExistCountSubquery
+ : (EXISTS | COUNT) SP? '{' SP? MATCH SP? oC_Pattern ( SP? oC_Where )? ( SP? kU_Hint )? SP? '}' ;
+
+oC_PropertyLookup
+ : '.' SP? ( oC_PropertyKeyName | STAR ) ;
+
+oC_CaseExpression
+ : ( ( CASE ( SP? oC_CaseAlternative )+ ) | ( CASE SP? oC_Expression ( SP? oC_CaseAlternative )+ ) ) ( SP? ELSE SP? oC_Expression )? SP? END ;
+
+oC_CaseAlternative
+ : WHEN SP? oC_Expression SP? THEN SP? oC_Expression ;
+
+oC_Variable
+ : oC_SymbolicName ;
+
+StringLiteral
+ : ( '"' ( StringLiteral_0 | EscapedChar )* '"' )
+ | ( '\'' ( StringLiteral_1 | EscapedChar )* '\'' )
+ ;
+
+EscapedChar
+ : '\\' ( '\\' | '\'' | '"' | ( 'B' | 'b' ) | ( 'F' | 'f' ) | ( 'N' | 'n' ) | ( 'R' | 'r' ) | ( 'T' | 't' ) | ( ( 'X' | 'x' ) ( HexDigit HexDigit ) ) | ( ( 'U' | 'u' ) ( HexDigit HexDigit HexDigit HexDigit ) ) | ( ( 'U' | 'u' ) ( HexDigit HexDigit HexDigit HexDigit HexDigit HexDigit HexDigit HexDigit ) ) ) ;
+
+oC_NumberLiteral
+ : oC_DoubleLiteral
+ | oC_IntegerLiteral
+ ;
+
+oC_Parameter
+ : '$' ( oC_SymbolicName | DecimalInteger ) ;
+
+oC_PropertyExpression
+ : oC_Atom SP? oC_PropertyLookup ;
+
+oC_PropertyKeyName
+ : oC_SchemaName ;
+
+oC_IntegerLiteral
+ : DecimalInteger ;
+
+DecimalInteger
+ : ZeroDigit
+ | ( NonZeroDigit ( Digit )* )
+ ;
+
+HexLetter
+ : ( 'A' | 'a' )
+ | ( 'B' | 'b' )
+ | ( 'C' | 'c' )
+ | ( 'D' | 'd' )
+ | ( 'E' | 'e' )
+ | ( 'F' | 'f' )
+ ;
+
+HexDigit
+ : Digit
+ | HexLetter
+ ;
+
+Digit
+ : ZeroDigit
+ | NonZeroDigit
+ ;
+
+NonZeroDigit
+ : NonZeroOctDigit
+ | '8'
+ | '9'
+ ;
+
+NonZeroOctDigit
+ : '1'
+ | '2'
+ | '3'
+ | '4'
+ | '5'
+ | '6'
+ | '7'
+ ;
+
+ZeroDigit
+ : '0' ;
+
+oC_DoubleLiteral
+ : ExponentDecimalReal
+ | RegularDecimalReal
+ ;
+
+ExponentDecimalReal
+ : ( ( Digit )+ | ( ( Digit )+ '.' ( Digit )+ ) | ( '.' ( Digit )+ ) ) ( 'E' | 'e' ) '-'? ( Digit )+ ;
+
+RegularDecimalReal
+ : ( Digit )* '.' ( Digit )+ ;
+
+oC_SchemaName
+ : oC_SymbolicName ;
+
+oC_SymbolicName
+ : UnescapedSymbolicName
+ | EscapedSymbolicName {if ($EscapedSymbolicName.text == "``") { notifyEmptyToken($EscapedSymbolicName); }}
+ | HexLetter
+ | kU_NonReservedKeywords
+ ;
+
+// example of BEGIN and END: TCKWith2.Scenario1
+kU_NonReservedKeywords
+ : COMMENT
+ | ADD
+ | ALTER
+ | AS
+ | ATTACH
+ | BEGIN
+ | BY
+ | CALL
+ | CHECKPOINT
+ | COMMENT
+ | COMMIT
+ | CONTAINS
+ | COPY
+ | COUNT
+ | CYCLE
+ | DATABASE
+ | DECIMAL
+ | DELETE
+ | DETACH
+ | DROP
+ | EXPLAIN
+ | EXPORT
+ | EXTENSION
+ | FORCE
+ | GRAPH
+ | IF
+ | IS
+ | IMPORT
+ | INCREMENT
+ | KEY
+ | LOAD
+ | LOGICAL
+ | MATCH
+ | MAXVALUE
+ | MERGE
+ | MINVALUE
+ | NO
+ | NODE
+ | PROJECT
+ | READ
+ | REL
+ | RENAME
+ | RETURN
+ | ROLLBACK
+ | ROLE
+ | SEQUENCE
+ | SET
+ | START
+ | STRUCT
+ | L_SKIP
+ | LIMIT
+ | TRANSACTION
+ | TYPE
+ | USE
+ | UNINSTALL
+ | UPDATE
+ | WRITE
+ | FROM
+ | TO
+ | YIELD
+ | USER
+ | PASSWORD
+ | MAP
+ ;
+
+UnescapedSymbolicName
+ : IdentifierStart ( IdentifierPart )* ;
+
+IdentifierStart
+ : ID_Start
+ | Pc
+ ;
+
+IdentifierPart
+ : ID_Continue
+ | Sc
+ ;
+
+EscapedSymbolicName
+ : ( '`' ( EscapedSymbolicName_0 )* '`' )+ ;
+
+SP
+ : ( WHITESPACE )+ ;
+
+WHITESPACE
+ : SPACE
+ | TAB
+ | LF
+ | VT
+ | FF
+ | CR
+ | FS
+ | GS
+ | RS
+ | US
+ | '\u1680'
+ | '\u180e'
+ | '\u2000'
+ | '\u2001'
+ | '\u2002'
+ | '\u2003'
+ | '\u2004'
+ | '\u2005'
+ | '\u2006'
+ | '\u2008'
+ | '\u2009'
+ | '\u200a'
+ | '\u2028'
+ | '\u2029'
+ | '\u205f'
+ | '\u3000'
+ | '\u00a0'
+ | '\u2007'
+ | '\u202f'
+ | CypherComment
+ ;
+
+CypherComment
+ : ( '/*' ( Comment_1 | ( '*' Comment_2 ) )* '*/' )
+ | ( '//' ( Comment_3 )* CR? ( LF | EOF ) )
+ ;
+
+oC_LeftArrowHead
+ : '<'
+ | '\u27e8'
+ | '\u3008'
+ | '\ufe64'
+ | '\uff1c'
+ ;
+
+oC_RightArrowHead
+ : '>'
+ | '\u27e9'
+ | '\u3009'
+ | '\ufe65'
+ | '\uff1e'
+ ;
+
+oC_Dash
+ : '-'
+ | '\u00ad'
+ | '\u2010'
+ | '\u2011'
+ | '\u2012'
+ | '\u2013'
+ | '\u2014'
+ | '\u2015'
+ | '\u2212'
+ | '\ufe58'
+ | '\ufe63'
+ | '\uff0d'
+ ;
+
+fragment FF : [\f] ;
+
+fragment EscapedSymbolicName_0 : ~[`] ;
+
+fragment RS : [\u001E] ;
+
+fragment ID_Continue : [\p{ID_Continue}] ;
+
+fragment Comment_1 : ~[*] ;
+
+fragment StringLiteral_1 : ~['\\] ;
+
+fragment Comment_3 : ~[\n\r] ;
+
+fragment Comment_2 : ~[/] ;
+
+fragment GS : [\u001D] ;
+
+fragment FS : [\u001C] ;
+
+fragment CR : [\r] ;
+
+fragment Sc : [\p{Sc}] ;
+
+fragment SPACE : [ ] ;
+
+fragment Pc : [\p{Pc}] ;
+
+fragment TAB : [\t] ;
+
+fragment StringLiteral_0 : ~["\\] ;
+
+fragment LF : [\n] ;
+
+fragment VT : [\u000B] ;
+
+fragment US : [\u001F] ;
+
+fragment ID_Start : [\p{ID_Start}] ;
+
+// This is used to capture unknown lexer input (e.g. !) to avoid parser exception.
+Unknown : .;
diff --git a/graph-wasm/lbug-0.12.2/lbug-src/src/antlr4/README.md b/graph-wasm/lbug-0.12.2/lbug-src/src/antlr4/README.md
new file mode 100644
index 0000000000..7bf65b6970
--- /dev/null
+++ b/graph-wasm/lbug-0.12.2/lbug-src/src/antlr4/README.md
@@ -0,0 +1 @@
+Neither `Cypher.g4` nor `keywords.txt` can be individually used to generate Ladybug's grammar. Rather, the files are combined to generate `scripts/antlr4/Cypher.g4`, which is immediately digestible.
diff --git a/graph-wasm/lbug-0.12.2/lbug-src/src/antlr4/keywords.txt b/graph-wasm/lbug-0.12.2/lbug-src/src/antlr4/keywords.txt
new file mode 100644
index 0000000000..563f123f3c
--- /dev/null
+++ b/graph-wasm/lbug-0.12.2/lbug-src/src/antlr4/keywords.txt
@@ -0,0 +1,115 @@
+ACYCLIC
+ANY
+ADD
+ALL
+ALTER
+AND
+AS
+ASC
+ASCENDING
+ATTACH
+BEGIN
+BY
+CALL
+CASE
+CAST
+CHECKPOINT
+COLUMN
+COMMENT
+COMMIT
+COMMIT_SKIP_CHECKPOINT
+CONTAINS
+COPY
+COUNT
+CREATE
+CYCLE
+DATABASE
+DBTYPE
+DEFAULT
+DELETE
+DESC
+DESCENDING
+DETACH
+DISTINCT
+DROP
+ELSE
+END
+ENDS
+EXISTS
+EXPLAIN
+EXPORT
+EXTENSION
+FALSE
+FROM
+FORCE
+GLOB
+GRAPH
+GROUP
+HEADERS
+HINT
+IMPORT
+IF
+IN
+INCREMENT
+INSTALL
+IS
+JOIN
+KEY
+LIMIT
+LOAD
+LOGICAL
+MACRO
+MATCH
+MAXVALUE
+MERGE
+MINVALUE
+MULTI_JOIN
+NO
+NODE
+NOT
+NONE
+NULL
+ON
+ONLY
+OPTIONAL
+OR
+ORDER
+PRIMARY
+PROFILE
+PROJECT
+READ
+REL
+RENAME
+RETURN
+ROLLBACK
+ROLLBACK_SKIP_CHECKPOINT
+SEQUENCE
+SET
+SHORTEST
+START
+STARTS
+STRUCT
+TABLE
+THEN
+TO
+TRAIL
+TRANSACTION
+TRUE
+TYPE
+UNION
+UNWIND
+UNINSTALL
+UPDATE
+USE
+WHEN
+WHERE
+WITH
+WRITE
+WSHORTEST
+XOR
+SINGLE
+YIELD
+USER
+PASSWORD
+ROLE
+MAP
diff --git a/graph-wasm/lbug-0.12.2/lbug-src/src/binder/CMakeLists.txt b/graph-wasm/lbug-0.12.2/lbug-src/src/binder/CMakeLists.txt
new file mode 100644
index 0000000000..8d8a03a51c
--- /dev/null
+++ b/graph-wasm/lbug-0.12.2/lbug-src/src/binder/CMakeLists.txt
@@ -0,0 +1,22 @@
+add_subdirectory(bind)
+add_subdirectory(bind_expression)
+add_subdirectory(ddl)
+add_subdirectory(expression)
+add_subdirectory(query)
+add_subdirectory(rewriter)
+add_subdirectory(visitor)
+
+add_library(lbug_binder
+ OBJECT
+ binder.cpp
+ binder_scope.cpp
+ bound_statement_result.cpp
+ bound_scan_source.cpp
+ bound_statement_rewriter.cpp
+ bound_statement_visitor.cpp
+ expression_binder.cpp
+ expression_visitor.cpp)
+
+set(ALL_OBJECT_FILES
+ ${ALL_OBJECT_FILES} $
+ PARENT_SCOPE)
diff --git a/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/CMakeLists.txt b/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/CMakeLists.txt
new file mode 100644
index 0000000000..04787014be
--- /dev/null
+++ b/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/CMakeLists.txt
@@ -0,0 +1,32 @@
+add_subdirectory(copy)
+add_subdirectory(ddl)
+add_subdirectory(read)
+
+add_library(
+ lbug_binder_bind
+ OBJECT
+ bind_attach_database.cpp
+ bind_create_macro.cpp
+ bind_ddl.cpp
+ bind_detach_database.cpp
+ bind_explain.cpp
+ bind_extension_clause.cpp
+ bind_file_scan.cpp
+ bind_graph_pattern.cpp
+ bind_projection_clause.cpp
+ bind_query.cpp
+ bind_reading_clause.cpp
+ bind_standalone_call.cpp
+ bind_table_function.cpp
+ bind_transaction.cpp
+ bind_updating_clause.cpp
+ bind_extension.cpp
+ bind_export_database.cpp
+ bind_import_database.cpp
+ bind_use_database.cpp
+ bind_standalone_call_function.cpp
+ bind_table_function.cpp)
+
+set(ALL_OBJECT_FILES
+ ${ALL_OBJECT_FILES} $
+ PARENT_SCOPE)
diff --git a/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/bind_attach_database.cpp b/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/bind_attach_database.cpp
new file mode 100644
index 0000000000..8dbc95f229
--- /dev/null
+++ b/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/bind_attach_database.cpp
@@ -0,0 +1,36 @@
+#include "binder/binder.h"
+#include "binder/bound_attach_database.h"
+#include "common/exception/binder.h"
+#include "common/string_utils.h"
+#include "parser/attach_database.h"
+#include "parser/expression/parsed_literal_expression.h"
+
+namespace lbug {
+namespace binder {
+
+static AttachInfo bindAttachInfo(const parser::AttachInfo& attachInfo) {
+ binder::AttachOption attachOption;
+ for (auto& [name, value] : attachInfo.options) {
+ if (value->getExpressionType() != common::ExpressionType::LITERAL) {
+ throw common::BinderException{"Attach option must be a literal expression."};
+ }
+ auto val = value->constPtrCast()->getValue();
+ attachOption.options.emplace(name, std::move(val));
+ }
+
+ if (common::StringUtils::getUpper(attachInfo.dbType) == common::ATTACHED_LBUG_DB_TYPE &&
+ attachInfo.dbAlias.empty()) {
+ throw common::BinderException{"Attaching a lbug database without an alias is not allowed."};
+ }
+ return binder::AttachInfo{attachInfo.dbPath, attachInfo.dbAlias, attachInfo.dbType,
+ std::move(attachOption)};
+}
+
+std::unique_ptr Binder::bindAttachDatabase(const parser::Statement& statement) {
+ auto& attachDatabase = statement.constCast();
+ auto boundAttachInfo = bindAttachInfo(attachDatabase.getAttachInfo());
+ return std::make_unique(std::move(boundAttachInfo));
+}
+
+} // namespace binder
+} // namespace lbug
diff --git a/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/bind_create_macro.cpp b/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/bind_create_macro.cpp
new file mode 100644
index 0000000000..7f80046efc
--- /dev/null
+++ b/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/bind_create_macro.cpp
@@ -0,0 +1,35 @@
+#include "binder/binder.h"
+#include "binder/bound_create_macro.h"
+#include "catalog/catalog.h"
+#include "common/exception/binder.h"
+#include "common/string_format.h"
+#include "common/string_utils.h"
+#include "parser/create_macro.h"
+#include "transaction/transaction.h"
+
+using namespace lbug::common;
+using namespace lbug::parser;
+
+namespace lbug {
+namespace binder {
+
+std::unique_ptr Binder::bindCreateMacro(const Statement& statement) const {
+ auto& createMacro = ku_dynamic_cast(statement);
+ auto macroName = createMacro.getMacroName();
+ StringUtils::toUpper(macroName);
+ if (catalog::Catalog::Get(*clientContext)
+ ->containsMacro(transaction::Transaction::Get(*clientContext), macroName)) {
+ throw BinderException{stringFormat("Macro {} already exists.", macroName)};
+ }
+ parser::default_macro_args defaultArgs;
+ for (auto& defaultArg : createMacro.getDefaultArgs()) {
+ defaultArgs.emplace_back(defaultArg.first, defaultArg.second->copy());
+ }
+ auto scalarMacro =
+ std::make_unique(createMacro.getMacroExpression()->copy(),
+ createMacro.getPositionalArgs(), std::move(defaultArgs));
+ return std::make_unique(std::move(macroName), std::move(scalarMacro));
+}
+
+} // namespace binder
+} // namespace lbug
diff --git a/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/bind_ddl.cpp b/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/bind_ddl.cpp
new file mode 100644
index 0000000000..3169cc7b3f
--- /dev/null
+++ b/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/bind_ddl.cpp
@@ -0,0 +1,492 @@
+#include "binder/binder.h"
+#include "binder/ddl/bound_alter.h"
+#include "binder/ddl/bound_create_sequence.h"
+#include "binder/ddl/bound_create_table.h"
+#include "binder/ddl/bound_create_type.h"
+#include "binder/ddl/bound_drop.h"
+#include "binder/expression_visitor.h"
+#include "catalog/catalog.h"
+#include "catalog/catalog_entry/node_table_catalog_entry.h"
+#include "catalog/catalog_entry/sequence_catalog_entry.h"
+#include "common/enums/extend_direction_util.h"
+#include "common/exception/binder.h"
+#include "common/exception/message.h"
+#include "common/string_format.h"
+#include "common/system_config.h"
+#include "common/types/types.h"
+#include "function/cast/functions/cast_from_string_functions.h"
+#include "function/sequence/sequence_functions.h"
+#include "main/client_context.h"
+#include "parser/ddl/alter.h"
+#include "parser/ddl/create_sequence.h"
+#include "parser/ddl/create_table.h"
+#include "parser/ddl/create_table_info.h"
+#include "parser/ddl/create_type.h"
+#include "parser/ddl/drop.h"
+#include "parser/expression/parsed_function_expression.h"
+#include "parser/expression/parsed_literal_expression.h"
+#include "transaction/transaction.h"
+
+using namespace lbug::common;
+using namespace lbug::parser;
+using namespace lbug::catalog;
+
+namespace lbug {
+namespace binder {
+
+static void validatePropertyName(const std::vector& definitions) {
+ case_insensitve_set_t nameSet;
+ for (auto& definition : definitions) {
+ if (nameSet.contains(definition.getName())) {
+ throw BinderException(stringFormat(
+ "Duplicated column name: {}, column name must be unique.", definition.getName()));
+ }
+ if (Binder::reservedInColumnName(definition.getName())) {
+ throw BinderException(
+ stringFormat("{} is a reserved property name.", definition.getName()));
+ }
+ nameSet.insert(definition.getName());
+ }
+}
+
+std::vector Binder::bindPropertyDefinitions(
+ const std::vector& parsedDefinitions, const std::string& tableName) {
+ std::vector definitions;
+ for (auto& def : parsedDefinitions) {
+ auto type = LogicalType::convertFromString(def.getType(), clientContext);
+ auto defaultExpr =
+ resolvePropertyDefault(def.defaultExpr.get(), type, tableName, def.getName());
+ auto boundExpr = expressionBinder.bindExpression(*defaultExpr);
+ if (boundExpr->dataType != type) {
+ expressionBinder.implicitCast(boundExpr, type);
+ }
+ auto columnDefinition = ColumnDefinition(def.getName(), std::move(type));
+ definitions.emplace_back(std::move(columnDefinition), std::move(defaultExpr));
+ }
+ validatePropertyName(definitions);
+ return definitions;
+}
+
+std::unique_ptr Binder::resolvePropertyDefault(ParsedExpression* parsedDefault,
+ const LogicalType& type, const std::string& tableName, const std::string& propertyName) {
+ if (parsedDefault == nullptr) { // No default provided.
+ if (type.getLogicalTypeID() == LogicalTypeID::SERIAL) {
+ auto serialName = SequenceCatalogEntry::getSerialName(tableName, propertyName);
+ auto literalExpr = std::make_unique(Value(serialName), "");
+ return std::make_unique(function::NextValFunction::name,
+ std::move(literalExpr), "" /* rawName */);
+ } else {
+ return std::make_unique(Value::createNullValue(type), "NULL");
+ }
+ } else {
+ if (type.getLogicalTypeID() == LogicalTypeID::SERIAL) {
+ throw BinderException("No DEFAULT value should be set for SERIAL columns");
+ }
+ return parsedDefault->copy();
+ }
+}
+
+static void validatePrimaryKey(const std::string& pkColName,
+ const std::vector& definitions) {
+ uint32_t primaryKeyIdx = UINT32_MAX;
+ for (auto i = 0u; i < definitions.size(); i++) {
+ if (definitions[i].getName() == pkColName) {
+ primaryKeyIdx = i;
+ }
+ }
+ if (primaryKeyIdx == UINT32_MAX) {
+ throw BinderException(
+ "Primary key " + pkColName + " does not match any of the predefined node properties.");
+ }
+ const auto& pkType = definitions[primaryKeyIdx].getType();
+ if (!pkType.isInternalType()) {
+ throw BinderException(ExceptionMessage::invalidPKType(pkType.toString()));
+ }
+ switch (pkType.getPhysicalType()) {
+ case PhysicalTypeID::UINT8:
+ case PhysicalTypeID::UINT16:
+ case PhysicalTypeID::UINT32:
+ case PhysicalTypeID::UINT64:
+ case PhysicalTypeID::INT8:
+ case PhysicalTypeID::INT16:
+ case PhysicalTypeID::INT32:
+ case PhysicalTypeID::INT64:
+ case PhysicalTypeID::INT128:
+ case PhysicalTypeID::UINT128:
+ case PhysicalTypeID::STRING:
+ case PhysicalTypeID::FLOAT:
+ case PhysicalTypeID::DOUBLE:
+ break;
+ default:
+ throw BinderException(ExceptionMessage::invalidPKType(pkType.toString()));
+ }
+}
+
+BoundCreateTableInfo Binder::bindCreateTableInfo(const CreateTableInfo* info) {
+ switch (info->type) {
+ case TableType::NODE: {
+ return bindCreateNodeTableInfo(info);
+ }
+ case TableType::REL: {
+ return bindCreateRelTableGroupInfo(info);
+ }
+ default: {
+ KU_UNREACHABLE;
+ }
+ }
+}
+
+BoundCreateTableInfo Binder::bindCreateNodeTableInfo(const CreateTableInfo* info) {
+ auto propertyDefinitions = bindPropertyDefinitions(info->propertyDefinitions, info->tableName);
+ auto& extraInfo = info->extraInfo->constCast();
+ validatePrimaryKey(extraInfo.pKName, propertyDefinitions);
+ auto boundExtraInfo = std::make_unique(extraInfo.pKName,
+ std::move(propertyDefinitions));
+ return BoundCreateTableInfo(CatalogEntryType::NODE_TABLE_ENTRY, info->tableName,
+ info->onConflict, std::move(boundExtraInfo), clientContext->useInternalCatalogEntry());
+}
+
+void Binder::validateNodeTableType(const TableCatalogEntry* entry) {
+ if (entry->getType() != CatalogEntryType::NODE_TABLE_ENTRY) {
+ throw BinderException(stringFormat("{} is not of type NODE.", entry->getName()));
+ }
+}
+
+void Binder::validateTableExistence(const main::ClientContext& context,
+ const std::string& tableName) {
+ auto transaction = transaction::Transaction::Get(context);
+ if (!Catalog::Get(context)->containsTable(transaction, tableName)) {
+ throw BinderException{stringFormat("Table {} does not exist.", tableName)};
+ }
+}
+
+void Binder::validateColumnExistence(const TableCatalogEntry* entry,
+ const std::string& columnName) {
+ if (!entry->containsProperty(columnName)) {
+ throw BinderException{
+ stringFormat("Column {} does not exist in table {}.", columnName, entry->getName())};
+ }
+}
+
+static ExtendDirection getStorageDirection(const case_insensitive_map_t& options) {
+ if (options.contains(TableOptionConstants::REL_STORAGE_DIRECTION_OPTION)) {
+ return ExtendDirectionUtil::fromString(
+ options.at(TableOptionConstants::REL_STORAGE_DIRECTION_OPTION).toString());
+ }
+ return DEFAULT_EXTEND_DIRECTION;
+}
+
+std::vector Binder::bindRelPropertyDefinitions(const CreateTableInfo& info) {
+ std::vector propertyDefinitions;
+ propertyDefinitions.emplace_back(
+ ColumnDefinition(InternalKeyword::ID, LogicalType::INTERNAL_ID()));
+ for (auto& definition : bindPropertyDefinitions(info.propertyDefinitions, info.tableName)) {
+ propertyDefinitions.push_back(definition.copy());
+ }
+ return propertyDefinitions;
+}
+
+BoundCreateTableInfo Binder::bindCreateRelTableGroupInfo(const CreateTableInfo* info) {
+ auto propertyDefinitions = bindRelPropertyDefinitions(*info);
+ auto& extraInfo = info->extraInfo->constCast();
+ auto srcMultiplicity = RelMultiplicityUtils::getFwd(extraInfo.relMultiplicity);
+ auto dstMultiplicity = RelMultiplicityUtils::getBwd(extraInfo.relMultiplicity);
+ auto boundOptions = bindParsingOptions(extraInfo.options);
+ auto storageDirection = getStorageDirection(boundOptions);
+ // Bind from to pairs
+ node_table_id_pair_set_t nodePairsSet;
+ std::vector nodePairs;
+ for (auto& [srcTableName, dstTableName] : extraInfo.srcDstTablePairs) {
+ auto srcEntry = bindNodeTableEntry(srcTableName);
+ validateNodeTableType(srcEntry);
+ auto dstEntry = bindNodeTableEntry(dstTableName);
+ validateNodeTableType(dstEntry);
+ NodeTableIDPair pair{srcEntry->getTableID(), dstEntry->getTableID()};
+ if (nodePairsSet.contains(pair)) {
+ throw BinderException(
+ stringFormat("Found duplicate FROM-TO {}-{} pairs.", srcTableName, dstTableName));
+ }
+ nodePairsSet.insert(pair);
+ nodePairs.emplace_back(pair);
+ }
+ auto boundExtraInfo =
+ std::make_unique(std::move(propertyDefinitions),
+ srcMultiplicity, dstMultiplicity, storageDirection, std::move(nodePairs));
+ return BoundCreateTableInfo(CatalogEntryType::REL_GROUP_ENTRY, info->tableName,
+ info->onConflict, std::move(boundExtraInfo), clientContext->useInternalCatalogEntry());
+}
+
+std::unique_ptr Binder::bindCreateTable(const Statement& statement) {
+ auto& createTable = statement.constCast();
+ if (createTable.getSource()) {
+ return bindCreateTableAs(createTable);
+ }
+ auto boundCreateInfo = bindCreateTableInfo(createTable.getInfo());
+ return std::make_unique(std::move(boundCreateInfo),
+ BoundStatementResult::createSingleStringColumnResult());
+}
+
+std::unique_ptr Binder::bindCreateTableAs(const Statement& statement) {
+ auto& createTable = statement.constCast();
+ auto boundInnerQuery = bindQuery(*createTable.getSource()->statement.get());
+ auto innerQueryResult = boundInnerQuery->getStatementResult();
+ auto columnNames = innerQueryResult->getColumnNames();
+ auto columnTypes = innerQueryResult->getColumnTypes();
+ std::vector propertyDefinitions;
+ propertyDefinitions.reserve(columnNames.size());
+ for (size_t i = 0; i < columnNames.size(); ++i) {
+ propertyDefinitions.emplace_back(
+ ColumnDefinition(std::string(columnNames[i]), columnTypes[i].copy()));
+ }
+ if (columnNames.empty()) {
+ throw BinderException("Subquery returns no columns");
+ }
+ auto createInfo = createTable.getInfo();
+ switch (createInfo->type) {
+ case TableType::NODE: {
+ // first column is primary key column temporarily for now
+ auto pkName = columnNames[0];
+ validatePrimaryKey(pkName, propertyDefinitions);
+ auto boundCopyFromInfo = bindCopyNodeFromInfo(createInfo->tableName, propertyDefinitions,
+ createTable.getSource(), options_t{}, columnNames, columnTypes, false /* byColumn */);
+ auto boundExtraInfo =
+ std::make_unique(pkName, std::move(propertyDefinitions));
+ auto boundCreateInfo = BoundCreateTableInfo(CatalogEntryType::NODE_TABLE_ENTRY,
+ createInfo->tableName, createInfo->onConflict, std::move(boundExtraInfo),
+ clientContext->useInternalCatalogEntry());
+ auto boundCreateTable = std::make_unique(std::move(boundCreateInfo),
+ BoundStatementResult::createSingleStringColumnResult());
+ boundCreateTable->setCopyInfo(std::move(boundCopyFromInfo));
+ return boundCreateTable;
+ }
+ case TableType::REL: {
+ auto& extraInfo = createInfo->extraInfo->constCast();
+ // Currently we don't support multiple from/to pairs for create rel table as
+ if (extraInfo.srcDstTablePairs.size() > 1) {
+ throw BinderException(
+ "Multiple FROM/TO pairs are not supported for CREATE REL TABLE AS.");
+ }
+ propertyDefinitions.insert(propertyDefinitions.begin(),
+ PropertyDefinition(ColumnDefinition(InternalKeyword::ID, LogicalType::INTERNAL_ID())));
+ auto catalog = Catalog::Get(*clientContext);
+ auto transaction = transaction::Transaction::Get(*clientContext);
+ auto fromTable =
+ catalog->getTableCatalogEntry(transaction, extraInfo.srcDstTablePairs[0].first)
+ ->ptrCast();
+ auto toTable =
+ catalog->getTableCatalogEntry(transaction, extraInfo.srcDstTablePairs[0].second)
+ ->ptrCast();
+ auto boundCreateInfo = bindCreateRelTableGroupInfo(createInfo);
+ auto boundCopyFromInfo = bindCopyRelFromInfo(createInfo->tableName, propertyDefinitions,
+ createTable.getSource(), options_t{}, columnNames, columnTypes, fromTable, toTable);
+ boundCreateInfo.extraInfo->ptrCast()->propertyDefinitions =
+ std::move(propertyDefinitions);
+ auto boundCreateTable = std::make_unique(std::move(boundCreateInfo),
+ BoundStatementResult::createSingleStringColumnResult());
+ boundCreateTable->setCopyInfo(std::move(boundCopyFromInfo));
+ return boundCreateTable;
+ }
+ default: {
+ KU_UNREACHABLE;
+ }
+ }
+}
+
+std::unique_ptr Binder::bindCreateType(const Statement& statement) const {
+ auto createType = statement.constPtrCast();
+ auto name = createType->getName();
+ LogicalType type = LogicalType::convertFromString(createType->getDataType(), clientContext);
+ auto transaction = transaction::Transaction::Get(*clientContext);
+ if (Catalog::Get(*clientContext)->containsType(transaction, name)) {
+ throw BinderException{stringFormat("Duplicated type name: {}.", name)};
+ }
+ return std::make_unique(std::move(name), std::move(type));
+}
+
+std::unique_ptr Binder::bindCreateSequence(const Statement& statement) const {
+ auto& createSequence = statement.constCast();
+ auto info = createSequence.getInfo();
+ auto sequenceName = info.sequenceName;
+ int64_t startWith = 0;
+ int64_t increment = 0;
+ int64_t minValue = 0;
+ int64_t maxValue = 0;
+ auto transaction = transaction::Transaction::Get(*clientContext);
+ switch (info.onConflict) {
+ case ConflictAction::ON_CONFLICT_THROW: {
+ if (Catalog::Get(*clientContext)->containsSequence(transaction, sequenceName)) {
+ throw BinderException(sequenceName + " already exists in catalog.");
+ }
+ } break;
+ default:
+ break;
+ }
+ auto literal = ku_string_t{info.increment.c_str(), info.increment.length()};
+ if (!function::CastString::tryCast(literal, increment)) {
+ throw BinderException("Out of bounds: SEQUENCE accepts integers within INT64.");
+ }
+ if (increment == 0) {
+ throw BinderException("INCREMENT must be non-zero.");
+ }
+
+ if (info.minValue == "") {
+ minValue = increment > 0 ? 1 : std::numeric_limits::min();
+ } else {
+ literal = ku_string_t{info.minValue.c_str(), info.minValue.length()};
+ if (!function::CastString::tryCast(literal, minValue)) {
+ throw BinderException("Out of bounds: SEQUENCE accepts integers within INT64.");
+ }
+ }
+ if (info.maxValue == "") {
+ maxValue = increment > 0 ? std::numeric_limits::max() : -1;
+ } else {
+ literal = ku_string_t{info.maxValue.c_str(), info.maxValue.length()};
+ if (!function::CastString::tryCast(literal, maxValue)) {
+ throw BinderException("Out of bounds: SEQUENCE accepts integers within INT64.");
+ }
+ }
+ if (info.startWith == "") {
+ startWith = increment > 0 ? minValue : maxValue;
+ } else {
+ literal = ku_string_t{info.startWith.c_str(), info.startWith.length()};
+ if (!function::CastString::tryCast(literal, startWith)) {
+ throw BinderException("Out of bounds: SEQUENCE accepts integers within INT64.");
+ }
+ }
+
+ if (maxValue < minValue) {
+ throw BinderException("SEQUENCE MAXVALUE should be greater than or equal to MINVALUE.");
+ }
+ if (startWith < minValue || startWith > maxValue) {
+ throw BinderException("SEQUENCE START value should be between MINVALUE and MAXVALUE.");
+ }
+
+ auto boundInfo = BoundCreateSequenceInfo(sequenceName, startWith, increment, minValue, maxValue,
+ info.cycle, info.onConflict, false /* isInternal */);
+ return std::make_unique(std::move(boundInfo));
+}
+
+std::unique_ptr Binder::bindDrop(const Statement& statement) {
+ auto& drop = statement.constCast();
+ return std::make_unique(drop.getDropInfo());
+}
+
+std::unique_ptr Binder::bindAlter(const Statement& statement) {
+ auto& alter = statement.constCast();
+ switch (alter.getInfo()->type) {
+ case AlterType::RENAME: {
+ return bindRenameTable(statement);
+ }
+ case AlterType::ADD_PROPERTY: {
+ return bindAddProperty(statement);
+ }
+ case AlterType::DROP_PROPERTY: {
+ return bindDropProperty(statement);
+ }
+ case AlterType::RENAME_PROPERTY: {
+ return bindRenameProperty(statement);
+ }
+ case AlterType::COMMENT: {
+ return bindCommentOn(statement);
+ }
+ case AlterType::ADD_FROM_TO_CONNECTION:
+ case AlterType::DROP_FROM_TO_CONNECTION: {
+ return bindAlterFromToConnection(statement);
+ }
+ default: {
+ KU_UNREACHABLE;
+ }
+ }
+}
+
+std::unique_ptr Binder::bindRenameTable(const Statement& statement) const {
+ auto& alter = statement.constCast();
+ auto info = alter.getInfo();
+ auto extraInfo = ku_dynamic_cast(info->extraInfo.get());
+ auto tableName = info->tableName;
+ auto newName = extraInfo->newName;
+ auto boundExtraInfo = std::make_unique(newName);
+ auto boundInfo =
+ BoundAlterInfo(AlterType::RENAME, tableName, std::move(boundExtraInfo), info->onConflict);
+ return std::make_unique(std::move(boundInfo));
+}
+
+std::unique_ptr Binder::bindAddProperty(const Statement& statement) {
+ auto& alter = statement.constCast();
+ auto info = alter.getInfo();
+ auto extraInfo = info->extraInfo->ptrCast();
+ auto tableName = info->tableName;
+ auto propertyName = extraInfo->propertyName;
+ auto type = LogicalType::convertFromString(extraInfo->dataType, clientContext);
+ auto columnDefinition = ColumnDefinition(propertyName, type.copy());
+ auto defaultExpr =
+ resolvePropertyDefault(extraInfo->defaultValue.get(), type, tableName, propertyName);
+ auto boundDefault = expressionBinder.bindExpression(*defaultExpr);
+ boundDefault = expressionBinder.implicitCastIfNecessary(boundDefault, type);
+ if (ConstantExpressionVisitor::needFold(*boundDefault)) {
+ boundDefault = expressionBinder.foldExpression(boundDefault);
+ }
+ auto propertyDefinition =
+ PropertyDefinition(std::move(columnDefinition), std::move(defaultExpr));
+ auto boundExtraInfo = std::make_unique(std::move(propertyDefinition),
+ std::move(boundDefault));
+ auto boundInfo = BoundAlterInfo(AlterType::ADD_PROPERTY, tableName, std::move(boundExtraInfo),
+ info->onConflict);
+ return std::make_unique(std::move(boundInfo));
+}
+
+std::unique_ptr Binder::bindDropProperty(const Statement& statement) const {
+ auto& alter = statement.constCast();
+ auto info = alter.getInfo();
+ auto extraInfo = info->extraInfo->constPtrCast();
+ auto tableName = info->tableName;
+ auto propertyName = extraInfo->propertyName;
+ auto boundExtraInfo = std::make_unique(propertyName);
+ auto boundInfo = BoundAlterInfo(AlterType::DROP_PROPERTY, tableName, std::move(boundExtraInfo),
+ info->onConflict);
+ return std::make_unique(std::move(boundInfo));
+}
+
+std::unique_ptr Binder::bindRenameProperty(const Statement& statement) const {
+ auto& alter = statement.constCast();
+ auto info = alter.getInfo();
+ auto extraInfo = info->extraInfo->constPtrCast();
+ auto tableName = info->tableName;
+ auto propertyName = extraInfo->propertyName;
+ auto newName = extraInfo->newName;
+ auto boundExtraInfo = std::make_unique(newName, propertyName);
+ auto boundInfo = BoundAlterInfo(AlterType::RENAME_PROPERTY, tableName,
+ std::move(boundExtraInfo), info->onConflict);
+ return std::make_unique(std::move(boundInfo));
+}
+
+std::unique_ptr Binder::bindCommentOn(const Statement& statement) const {
+ auto& alter = statement.constCast();
+ auto info = alter.getInfo();
+ auto extraInfo = info->extraInfo->constPtrCast();
+ auto tableName = info->tableName;
+ auto comment = extraInfo->comment;
+ auto boundExtraInfo = std::make_unique(comment);
+ auto boundInfo =
+ BoundAlterInfo(AlterType::COMMENT, tableName, std::move(boundExtraInfo), info->onConflict);
+ return std::make_unique(std::move(boundInfo));
+}
+
+std::unique_ptr Binder::bindAlterFromToConnection(
+ const Statement& statement) const {
+ auto& alter = statement.constCast();
+ auto info = alter.getInfo();
+ auto extraInfo = info->extraInfo->constPtrCast();
+ auto tableName = info->tableName;
+ auto srcTableEntry = bindNodeTableEntry(extraInfo->srcTableName);
+ auto dstTableEntry = bindNodeTableEntry(extraInfo->dstTableName);
+ auto srcTableID = srcTableEntry->getTableID();
+ auto dstTableID = dstTableEntry->getTableID();
+ auto boundExtraInfo = std::make_unique(srcTableID, dstTableID);
+ auto boundInfo =
+ BoundAlterInfo(info->type, tableName, std::move(boundExtraInfo), info->onConflict);
+ return std::make_unique(std::move(boundInfo));
+}
+
+} // namespace binder
+} // namespace lbug
diff --git a/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/bind_detach_database.cpp b/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/bind_detach_database.cpp
new file mode 100644
index 0000000000..5506e9afc2
--- /dev/null
+++ b/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/bind_detach_database.cpp
@@ -0,0 +1,14 @@
+#include "binder/binder.h"
+#include "binder/bound_detach_database.h"
+#include "parser/detach_database.h"
+
+namespace lbug {
+namespace binder {
+
+std::unique_ptr Binder::bindDetachDatabase(const parser::Statement& statement) {
+ auto& detachDatabase = statement.constCast();
+ return std::make_unique(detachDatabase.getDBName());
+}
+
+} // namespace binder
+} // namespace lbug
diff --git a/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/bind_explain.cpp b/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/bind_explain.cpp
new file mode 100644
index 0000000000..789d246c4b
--- /dev/null
+++ b/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/bind_explain.cpp
@@ -0,0 +1,16 @@
+#include "binder/binder.h"
+#include "binder/bound_explain.h"
+#include "parser/explain_statement.h"
+
+namespace lbug {
+namespace binder {
+
+std::unique_ptr Binder::bindExplain(const parser::Statement& statement) {
+ auto& explain = statement.constCast();
+ auto boundStatementToExplain = bind(*explain.getStatementToExplain());
+ return std::make_unique(std::move(boundStatementToExplain),
+ explain.getExplainType());
+}
+
+} // namespace binder
+} // namespace lbug
diff --git a/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/bind_export_database.cpp b/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/bind_export_database.cpp
new file mode 100644
index 0000000000..3da7586a27
--- /dev/null
+++ b/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/bind_export_database.cpp
@@ -0,0 +1,169 @@
+#include "binder/bound_export_database.h"
+#include "binder/query/bound_regular_query.h"
+#include "catalog/catalog.h"
+#include "catalog/catalog_entry/index_catalog_entry.h"
+#include "catalog/catalog_entry/node_table_catalog_entry.h"
+#include "catalog/catalog_entry/rel_group_catalog_entry.h"
+#include "common/exception/binder.h"
+#include "common/file_system/virtual_file_system.h"
+#include "common/string_utils.h"
+#include "main/client_context.h"
+#include "parser/parser.h"
+#include "parser/port_db.h"
+#include "parser/query/regular_query.h"
+#include "transaction/transaction.h"
+
+using namespace lbug::binder;
+using namespace lbug::common;
+using namespace lbug::parser;
+using namespace lbug::catalog;
+using namespace lbug::transaction;
+using namespace lbug::storage;
+
+namespace lbug {
+namespace binder {
+
+FileTypeInfo getFileType(case_insensitive_map_t& options) {
+ auto fileTypeInfo =
+ FileTypeInfo{FileType::PARQUET, PortDBConstants::DEFAULT_EXPORT_FORMAT_OPTION};
+ if (options.contains(PortDBConstants::EXPORT_FORMAT_OPTION)) {
+ auto value = options.at(PortDBConstants::EXPORT_FORMAT_OPTION);
+ if (value.getDataType().getLogicalTypeID() != LogicalTypeID::STRING) {
+ throw BinderException("The type of format option must be a string.");
+ }
+ auto valueStr = value.getValue();
+ StringUtils::toUpper(valueStr);
+ fileTypeInfo = FileTypeInfo{FileTypeUtils::fromString(valueStr), valueStr};
+ options.erase(PortDBConstants::EXPORT_FORMAT_OPTION);
+ }
+ return fileTypeInfo;
+}
+
+void bindExportTableData(ExportedTableData& tableData, const std::string& query,
+ main::ClientContext* context, Binder* binder) {
+ auto parsedStatement = Parser::parseQuery(query);
+ KU_ASSERT(parsedStatement.size() == 1);
+ auto parsedQuery = parsedStatement[0]->constPtrCast();
+ context->setUseInternalCatalogEntry(true /* useInternalCatalogEntry */);
+ auto boundQuery = binder->bindQuery(*parsedQuery);
+ context->setUseInternalCatalogEntry(false /* useInternalCatalogEntry */);
+ auto columns = boundQuery->getStatementResult()->getColumns();
+ for (auto& column : columns) {
+ auto columnName = column->hasAlias() ? column->getAlias() : column->toString();
+ tableData.columnNames.push_back(columnName);
+ tableData.columnTypes.push_back(column->getDataType().copy());
+ }
+ tableData.regularQuery = std::move(boundQuery);
+}
+
+static std::string getExportNodeTableDataQuery(const TableCatalogEntry& entry) {
+ return stringFormat("match (a:`{}`) return a.*", entry.getName());
+}
+
+static std::string getExportRelTableDataQuery(const TableCatalogEntry& relGroupEntry,
+ const NodeTableCatalogEntry& srcEntry, const NodeTableCatalogEntry& dstEntry) {
+ return stringFormat("match (a:`{}`)-[r:`{}`]->(b:`{}`) return a.{},b.{},r.*;",
+ srcEntry.getName(), relGroupEntry.getName(), dstEntry.getName(),
+ srcEntry.getPrimaryKeyName(), dstEntry.getPrimaryKeyName());
+}
+
+static std::vector getExportInfo(const Catalog& catalog,
+ main::ClientContext* context, Binder* binder, FileTypeInfo& fileTypeInfo) {
+ auto transaction = Transaction::Get(*context);
+ std::vector exportData;
+ for (auto entry : catalog.getNodeTableEntries(transaction, false /*useInternal*/)) {
+ ExportedTableData tableData;
+ tableData.tableName = entry->getName();
+ tableData.fileName =
+ entry->getName() + "." + StringUtils::getLower(fileTypeInfo.fileTypeStr);
+ auto query = getExportNodeTableDataQuery(*entry);
+ bindExportTableData(tableData, query, context, binder);
+ exportData.push_back(std::move(tableData));
+ }
+ for (auto entry : catalog.getRelGroupEntries(transaction, false /* useInternal */)) {
+ auto& relGroupEntry = entry->constCast();
+ for (auto& info : relGroupEntry.getRelEntryInfos()) {
+ ExportedTableData tableData;
+ auto srcTableID = info.nodePair.srcTableID;
+ auto dstTableID = info.nodePair.dstTableID;
+ auto& srcEntry = catalog.getTableCatalogEntry(transaction, srcTableID)
+ ->constCast();
+ auto& dstEntry = catalog.getTableCatalogEntry(transaction, dstTableID)
+ ->constCast();
+ tableData.tableName = entry->getName();
+ tableData.fileName =
+ stringFormat("{}_{}_{}.{}", relGroupEntry.getName(), srcEntry.getName(),
+ dstEntry.getName(), StringUtils::getLower(fileTypeInfo.fileTypeStr));
+ auto query = getExportRelTableDataQuery(relGroupEntry, srcEntry, dstEntry);
+ bindExportTableData(tableData, query, context, binder);
+ exportData.push_back(std::move(tableData));
+ }
+ }
+
+ for (auto indexEntry : catalog.getIndexEntries(transaction)) {
+ // Export
+ ExportedTableData tableData;
+ auto entry = indexEntry->getTableEntryToExport(context);
+ if (entry == nullptr) {
+ continue;
+ }
+ KU_ASSERT(entry->getTableType() == TableType::NODE);
+ tableData.tableName = entry->getName();
+ tableData.fileName =
+ entry->getName() + "." + StringUtils::getLower(fileTypeInfo.fileTypeStr);
+ auto query = getExportNodeTableDataQuery(*entry);
+ bindExportTableData(tableData, query, context, binder);
+ exportData.push_back(std::move(tableData));
+ }
+ return exportData;
+}
+
+static bool schemaOnly(case_insensitive_map_t& parsedOptions,
+ const parser::ExportDB& exportDB) {
+ auto isSchemaOnlyOption = [](const std::pair& option) -> bool {
+ if (option.first != PortDBConstants::SCHEMA_ONLY_OPTION) {
+ return false;
+ }
+ if (option.second.getDataType() != LogicalType::BOOL()) {
+ throw common::BinderException{common::stringFormat(
+ "The '{}' option must have a BOOL value.", PortDBConstants::SCHEMA_ONLY_OPTION)};
+ }
+ return option.second.getValue();
+ };
+ auto exportSchemaOnly =
+ std::count_if(parsedOptions.begin(), parsedOptions.end(), isSchemaOnlyOption) != 0;
+ if (exportSchemaOnly && exportDB.getParsingOptionsRef().size() != 1) {
+ throw common::BinderException{
+ common::stringFormat("When '{}' option is set to true in export "
+ "database, no other options are allowed.",
+ PortDBConstants::SCHEMA_ONLY_OPTION)};
+ }
+ parsedOptions.erase(PortDBConstants::SCHEMA_ONLY_OPTION);
+ return exportSchemaOnly;
+}
+
+std::unique_ptr Binder::bindExportDatabaseClause(const Statement& statement) {
+ auto& exportDB = statement.constCast();
+ auto parsedOptions = bindParsingOptions(exportDB.getParsingOptionsRef());
+ auto fileTypeInfo = getFileType(parsedOptions);
+ switch (fileTypeInfo.fileType) {
+ case FileType::CSV:
+ case FileType::PARQUET:
+ break;
+ default:
+ throw BinderException("Export database currently only supports csv and parquet files.");
+ }
+ auto exportSchemaOnly = schemaOnly(parsedOptions, exportDB);
+ if (!exportSchemaOnly && fileTypeInfo.fileType != FileType::CSV && parsedOptions.size() != 0) {
+ throw BinderException{"Only export to csv can have options."};
+ }
+ auto exportData =
+ getExportInfo(*Catalog::Get(*clientContext), clientContext, this, fileTypeInfo);
+ auto boundFilePath = VirtualFileSystem::GetUnsafe(*clientContext)
+ ->expandPath(clientContext, exportDB.getFilePath());
+ return std::make_unique(boundFilePath, fileTypeInfo, std::move(exportData),
+ std::move(parsedOptions), exportSchemaOnly);
+}
+
+} // namespace binder
+} // namespace lbug
diff --git a/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/bind_extension.cpp b/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/bind_extension.cpp
new file mode 100644
index 0000000000..1ba10b366c
--- /dev/null
+++ b/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/bind_extension.cpp
@@ -0,0 +1,79 @@
+#include "binder/binder.h"
+#include "binder/bound_extension_statement.h"
+#include "common/exception/binder.h"
+#include "common/file_system/local_file_system.h"
+#include "common/string_utils.h"
+#include "extension/extension.h"
+#include "parser/extension_statement.h"
+
+using namespace lbug::parser;
+
+namespace lbug {
+namespace binder {
+
+static void bindInstallExtension(const ExtensionAuxInfo& auxInfo) {
+ if (!ExtensionUtils::isOfficialExtension(auxInfo.path)) {
+ throw common::BinderException(
+ common::stringFormat("{} is not an official extension.\nNon-official extensions "
+ "can be installed directly by: `LOAD EXTENSION [EXTENSION_PATH]`.",
+ auxInfo.path));
+ }
+}
+
+static void bindLoadExtension(main::ClientContext* context, const ExtensionAuxInfo& auxInfo) {
+ auto localFileSystem = common::LocalFileSystem("");
+ if (ExtensionUtils::isOfficialExtension(auxInfo.path)) {
+ auto extensionName = common::StringUtils::getLower(auxInfo.path);
+ if (!localFileSystem.fileOrPathExists(
+ ExtensionUtils::getLocalPathForExtensionLib(context, extensionName))) {
+ throw common::BinderException(
+ common::stringFormat("Extension: {} is an official extension and has not been "
+ "installed.\nYou can install it by: install {}.",
+ extensionName, extensionName));
+ }
+ return;
+ }
+ if (!localFileSystem.fileOrPathExists(auxInfo.path, nullptr /* clientContext */)) {
+ throw common::BinderException(
+ common::stringFormat("The extension {} is neither an official extension, nor does "
+ "the extension path: '{}' exists.",
+ auxInfo.path, auxInfo.path));
+ }
+}
+
+static void bindUninstallExtension(const ExtensionAuxInfo& auxInfo) {
+ if (!ExtensionUtils::isOfficialExtension(auxInfo.path)) {
+ throw common::BinderException(
+ common::stringFormat("The extension {} is not an official extension.\nOnly official "
+ "extensions can be uninstalled.",
+ auxInfo.path));
+ }
+}
+
+std::unique_ptr Binder::bindExtension(const Statement& statement) {
+#ifdef __WASM__
+ throw common::BinderException{"Extensions are not available in the WASM environment"};
+#endif
+ auto extensionStatement = statement.constPtrCast();
+ auto auxInfo = extensionStatement->getAuxInfo();
+ switch (auxInfo->action) {
+ case ExtensionAction::INSTALL:
+ bindInstallExtension(*auxInfo);
+ break;
+ case ExtensionAction::LOAD:
+ bindLoadExtension(clientContext, *auxInfo);
+ break;
+ case ExtensionAction::UNINSTALL:
+ bindUninstallExtension(*auxInfo);
+ break;
+ default:
+ KU_UNREACHABLE;
+ }
+ if (ExtensionUtils::isOfficialExtension(auxInfo->path)) {
+ common::StringUtils::toLower(auxInfo->path);
+ }
+ return std::make_unique(std::move(auxInfo));
+}
+
+} // namespace binder
+} // namespace lbug
diff --git a/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/bind_extension_clause.cpp b/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/bind_extension_clause.cpp
new file mode 100644
index 0000000000..af937fc206
--- /dev/null
+++ b/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/bind_extension_clause.cpp
@@ -0,0 +1,21 @@
+#include "binder/binder.h"
+#include "extension/binder_extension.h"
+
+using namespace lbug::common;
+using namespace lbug::parser;
+
+namespace lbug {
+namespace binder {
+
+std::unique_ptr Binder::bindExtensionClause(const parser::Statement& statement) {
+ for (auto& binderExtension : binderExtensions) {
+ auto boundStatement = binderExtension->bind(statement);
+ if (boundStatement) {
+ return boundStatement;
+ }
+ }
+ KU_UNREACHABLE;
+}
+
+} // namespace binder
+} // namespace lbug
diff --git a/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/bind_file_scan.cpp b/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/bind_file_scan.cpp
new file mode 100644
index 0000000000..1627f3ee92
--- /dev/null
+++ b/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/bind_file_scan.cpp
@@ -0,0 +1,312 @@
+#include "binder/binder.h"
+#include "binder/bound_scan_source.h"
+#include "binder/expression/literal_expression.h"
+#include "binder/expression/parameter_expression.h"
+#include "common/exception/binder.h"
+#include "common/exception/copy.h"
+#include "common/exception/message.h"
+#include "common/file_system/local_file_system.h"
+#include "common/file_system/virtual_file_system.h"
+#include "common/string_format.h"
+#include "common/string_utils.h"
+#include "extension/extension_manager.h"
+#include "function/table/bind_input.h"
+#include "main/client_context.h"
+#include "main/database_manager.h"
+#include "parser/expression/parsed_function_expression.h"
+#include "parser/scan_source.h"
+
+using namespace lbug::parser;
+using namespace lbug::binder;
+using namespace lbug::common;
+using namespace lbug::function;
+using namespace lbug::catalog;
+
+namespace lbug {
+namespace binder {
+
+FileTypeInfo bindSingleFileType(const main::ClientContext* context, const std::string& filePath) {
+ std::filesystem::path fileName(filePath);
+ auto extension = VirtualFileSystem::GetUnsafe(*context)->getFileExtension(fileName);
+ return FileTypeInfo{FileTypeUtils::getFileTypeFromExtension(extension),
+ extension.substr(std::min(1, extension.length()))};
+}
+
+FileTypeInfo Binder::bindFileTypeInfo(const std::vector& filePaths) const {
+ auto expectedFileType = FileTypeInfo{FileType::UNKNOWN, "" /* fileTypeStr */};
+ for (auto& filePath : filePaths) {
+ auto fileType = bindSingleFileType(clientContext, filePath);
+ expectedFileType =
+ (expectedFileType.fileType == FileType::UNKNOWN) ? fileType : expectedFileType;
+ if (fileType.fileType != expectedFileType.fileType) {
+ throw CopyException("Loading files with different types is not currently supported.");
+ }
+ }
+ return expectedFileType;
+}
+
+std::vector Binder::bindFilePaths(const std::vector& filePaths) const {
+ std::vector boundFilePaths;
+ for (auto& filePath : filePaths) {
+ // This is a temporary workaround because we use duckdb to read from iceberg/delta/azure.
+ // When we read delta/iceberg/azure tables from s3/httpfs, we don't have the httpfs
+ // extension loaded meaning that we cannot handle remote paths. So we pass the file path to
+ // duckdb for validation when we bindFileScanSource.
+ const auto& loadedExtensions =
+ extension::ExtensionManager::Get(*clientContext)->getLoadedExtensions();
+ const bool httpfsExtensionLoaded =
+ std::any_of(loadedExtensions.begin(), loadedExtensions.end(),
+ [](const auto& extension) { return extension.getExtensionName() == "HTTPFS"; });
+ if (!httpfsExtensionLoaded && !LocalFileSystem::isLocalPath(filePath)) {
+ boundFilePaths.push_back(filePath);
+ continue;
+ }
+ auto globbedFilePaths =
+ VirtualFileSystem::GetUnsafe(*clientContext)->glob(clientContext, filePath);
+ if (globbedFilePaths.empty()) {
+ throw BinderException{
+ stringFormat("No file found that matches the pattern: {}.", filePath)};
+ }
+ for (auto& globbedPath : globbedFilePaths) {
+ boundFilePaths.push_back(globbedPath);
+ }
+ }
+ return boundFilePaths;
+}
+
+case_insensitive_map_t Binder::bindParsingOptions(const options_t& parsingOptions) {
+ case_insensitive_map_t options;
+ for (auto& option : parsingOptions) {
+ auto name = option.first;
+ StringUtils::toUpper(name);
+ auto expr = expressionBinder.bindExpression(*option.second);
+ KU_ASSERT(expr->expressionType == ExpressionType::LITERAL);
+ auto literalExpr = ku_dynamic_cast(expr.get());
+ options.insert({name, literalExpr->getValue()});
+ }
+ return options;
+}
+
+std::unique_ptr Binder::bindScanSource(const BaseScanSource* source,
+ const options_t& options, const std::vector& columnNames,
+ const std::vector& columnTypes) {
+ switch (source->type) {
+ case ScanSourceType::FILE: {
+ return bindFileScanSource(*source, options, columnNames, columnTypes);
+ }
+ case ScanSourceType::QUERY: {
+ return bindQueryScanSource(*source, options, columnNames, columnTypes);
+ }
+ case ScanSourceType::OBJECT: {
+ return bindObjectScanSource(*source, options, columnNames, columnTypes);
+ }
+ case ScanSourceType::TABLE_FUNC: {
+ return bindTableFuncScanSource(*source, options, columnNames, columnTypes);
+ }
+ case ScanSourceType::PARAM: {
+ return bindParameterScanSource(*source, options, columnNames, columnTypes);
+ }
+ default:
+ KU_UNREACHABLE;
+ }
+}
+
+bool handleFileViaFunction(main::ClientContext* context, std::vector filePaths) {
+ bool handleFileViaFunction = false;
+ if (VirtualFileSystem::GetUnsafe(*context)->fileOrPathExists(filePaths[0], context)) {
+ handleFileViaFunction =
+ VirtualFileSystem::GetUnsafe(*context)->handleFileViaFunction(filePaths[0]);
+ }
+ return handleFileViaFunction;
+}
+
+std::unique_ptr Binder::bindFileScanSource(const BaseScanSource& scanSource,
+ const options_t& options, const std::vector& columnNames,
+ const std::vector& columnTypes) {
+ auto fileSource = scanSource.constPtrCast();
+ auto filePaths = bindFilePaths(fileSource->filePaths);
+ auto boundOptions = bindParsingOptions(options);
+ FileTypeInfo fileTypeInfo;
+
+ if (boundOptions.contains(FileScanInfo::FILE_FORMAT_OPTION_NAME)) {
+ auto fileFormat = boundOptions.at(FileScanInfo::FILE_FORMAT_OPTION_NAME).toString();
+ fileTypeInfo = FileTypeInfo{FileTypeUtils::fromString(fileFormat), fileFormat};
+ } else {
+ fileTypeInfo = bindFileTypeInfo(filePaths);
+ }
+ // If we defined a certain FileType, we have to ensure the path is a file, not something else
+ // (e.g. an existed directory)
+ if (fileTypeInfo.fileType != FileType::UNKNOWN) {
+ for (const auto& filePath : filePaths) {
+ if (!LocalFileSystem::fileExists(filePath) && LocalFileSystem::isLocalPath(filePath)) {
+ throw BinderException{stringFormat("Provided path is not a file: {}.", filePath)};
+ }
+ }
+ }
+ boundOptions.erase(FileScanInfo::FILE_FORMAT_OPTION_NAME);
+ // Bind file configuration
+ auto fileScanInfo = std::make_unique(std::move(fileTypeInfo), filePaths);
+ fileScanInfo->options = std::move(boundOptions);
+ TableFunction func;
+ if (handleFileViaFunction(clientContext, filePaths)) {
+ func = VirtualFileSystem::GetUnsafe(*clientContext)->getHandleFunction(filePaths[0]);
+ } else {
+ func = getScanFunction(fileScanInfo->fileTypeInfo, *fileScanInfo);
+ }
+ // Bind table function
+ auto bindInput = TableFuncBindInput();
+ bindInput.addLiteralParam(Value::createValue(filePaths[0]));
+ auto extraInput = std::make_unique();
+ extraInput->fileScanInfo = fileScanInfo->copy();
+ extraInput->expectedColumnNames = columnNames;
+ extraInput->expectedColumnTypes = LogicalType::copy(columnTypes);
+ extraInput->tableFunction = &func;
+ bindInput.extraInput = std::move(extraInput);
+ bindInput.binder = this;
+ auto bindData = func.bindFunc(clientContext, &bindInput);
+ auto info = BoundTableScanInfo(func, std::move(bindData));
+ return std::make_unique(ScanSourceType::FILE, std::move(info));
+}
+
+std::unique_ptr Binder::bindQueryScanSource(const BaseScanSource& scanSource,
+ const options_t& options, const std::vector& columnNames,
+ const std::vector&) {
+ auto querySource = scanSource.constPtrCast();
+ auto boundStatement = bind(*querySource->statement);
+ auto columns = boundStatement->getStatementResult()->getColumns();
+ if (columns.size() != columnNames.size()) {
+ throw BinderException(stringFormat("Query returns {} columns but {} columns were expected.",
+ columns.size(), columnNames.size()));
+ }
+ for (auto i = 0u; i < columns.size(); ++i) {
+ columns[i]->setAlias(columnNames[i]);
+ }
+ auto scanInfo = BoundQueryScanSourceInfo(bindParsingOptions(options));
+ return std::make_unique(std::move(boundStatement), std::move(scanInfo));
+}
+
+static TableFunction getObjectScanFunc(const std::string& dbName, const std::string& tableName,
+ main::ClientContext* clientContext) {
+ // Bind external database table
+ auto attachedDB = main::DatabaseManager::Get(*clientContext)->getAttachedDatabase(dbName);
+ auto attachedCatalog = attachedDB->getCatalog();
+ auto entry = attachedCatalog->getTableCatalogEntry(
+ transaction::Transaction::Get(*clientContext), tableName);
+ return entry->ptrCast()->getScanFunction();
+}
+
+BoundTableScanInfo bindTableScanSourceInfo(Binder& binder, TableFunction func,
+ const std::string& sourceName, std::unique_ptr bindData,
+ const std::vector& columnNames, const std::vector& columnTypes) {
+ expression_vector columns;
+ if (columnTypes.empty()) {
+ } else {
+ if (bindData->getNumColumns() != columnTypes.size()) {
+ throw BinderException(stringFormat("{} has {} columns but {} columns were expected.",
+ sourceName, bindData->getNumColumns(), columnTypes.size()));
+ }
+ for (auto i = 0u; i < bindData->getNumColumns(); ++i) {
+ auto column =
+ binder.createInvisibleVariable(columnNames[i], bindData->columns[i]->getDataType());
+ binder.replaceExpressionInScope(bindData->columns[i]->toString(), columnNames[i],
+ column);
+ columns.push_back(column);
+ }
+ bindData->columns = columns;
+ }
+ return BoundTableScanInfo(func, std::move(bindData));
+}
+
+std::unique_ptr Binder::bindParameterScanSource(
+ const BaseScanSource& scanSource, const options_t& options,
+ const std::vector& columnNames, const std::vector& columnTypes) {
+ auto paramSource = scanSource.constPtrCast();
+ auto paramExpr = expressionBinder.bindParameterExpression(*paramSource->paramExpression);
+ auto scanSourceValue = paramExpr->constCast().getValue();
+ if (scanSourceValue.getDataType().getLogicalTypeID() != LogicalTypeID::POINTER) {
+ throw BinderException(stringFormat(
+ "Trying to scan from unsupported data type {}. The only parameter types that can be "
+ "scanned from are pandas/polars dataframes and pyarrow tables.",
+ scanSourceValue.getDataType().toString()));
+ }
+ TableFunction func;
+ std::unique_ptr bindData;
+ auto bindInput = TableFuncBindInput();
+ bindInput.binder = this;
+ // Bind external object as table
+ auto replacementData =
+ clientContext->tryReplaceByHandle(scanSourceValue.getValue());
+ func = replacementData->func;
+ auto replaceExtraInput = std::make_unique();
+ replaceExtraInput->fileScanInfo.options = bindParsingOptions(options);
+ replacementData->bindInput.extraInput = std::move(replaceExtraInput);
+ replacementData->bindInput.binder = this;
+ bindData = func.bindFunc(clientContext, &replacementData->bindInput);
+ auto info = bindTableScanSourceInfo(*this, func, paramExpr->toString(), std::move(bindData),
+ columnNames, columnTypes);
+ return std::make_unique(ScanSourceType::OBJECT, std::move(info));
+}
+
+std::unique_ptr Binder::bindObjectScanSource(const BaseScanSource& scanSource,
+ const options_t& options, const std::vector& columnNames,
+ const std::vector& columnTypes) {
+ auto objectSource = scanSource.constPtrCast();
+ TableFunction func;
+ std::unique_ptr bindData;
+ std::string objectName;
+ auto bindInput = TableFuncBindInput();
+ bindInput.binder = this;
+ if (objectSource->objectNames.size() == 1) {
+ // Bind external object as table
+ objectName = objectSource->objectNames[0];
+ auto replacementData = clientContext->tryReplaceByName(objectName);
+ if (replacementData != nullptr) { // Replace as python object
+ func = replacementData->func;
+ auto replaceExtraInput = std::make_unique();
+ replaceExtraInput->fileScanInfo.options = bindParsingOptions(options);
+ replacementData->bindInput.extraInput = std::move(replaceExtraInput);
+ replacementData->bindInput.binder = this;
+ bindData = func.bindFunc(clientContext, &replacementData->bindInput);
+ } else if (main::DatabaseManager::Get(*clientContext)->hasDefaultDatabase()) {
+ auto dbName = main::DatabaseManager::Get(*clientContext)->getDefaultDatabase();
+ func = getObjectScanFunc(dbName, objectSource->objectNames[0], clientContext);
+ bindData = func.bindFunc(clientContext, &bindInput);
+ } else {
+ throw BinderException(ExceptionMessage::variableNotInScope(objectName));
+ }
+ } else if (objectSource->objectNames.size() == 2) {
+ // Bind external database table
+ objectName = objectSource->objectNames[0] + "." + objectSource->objectNames[1];
+ func = getObjectScanFunc(objectSource->objectNames[0], objectSource->objectNames[1],
+ clientContext);
+ bindData = func.bindFunc(clientContext, &bindInput);
+ } else {
+ // LCOV_EXCL_START
+ throw BinderException(stringFormat("Cannot find object {}.",
+ StringUtils::join(objectSource->objectNames, ",")));
+ // LCOV_EXCL_STOP
+ }
+ auto info = bindTableScanSourceInfo(*this, func, objectName, std::move(bindData), columnNames,
+ columnTypes);
+ return std::make_unique(ScanSourceType::OBJECT, std::move(info));
+}
+
+std::unique_ptr Binder::bindTableFuncScanSource(
+ const BaseScanSource& scanSource, const options_t& options,
+ const std::vector& columnNames, const std::vector& columnTypes) {
+ if (!options.empty()) {
+ throw common::BinderException{"No option is supported when copying from table functions."};
+ }
+ auto tableFuncScanSource = scanSource.constPtrCast();
+ auto& parsedFuncExpression =
+ tableFuncScanSource->functionExpression->constCast();
+ auto boundTableFunc = bindTableFunc(parsedFuncExpression.getFunctionName(),
+ *tableFuncScanSource->functionExpression, {} /* yieldVariables */);
+ auto& tableFunc = boundTableFunc.func;
+ auto info = bindTableScanSourceInfo(*this, tableFunc, tableFunc.name,
+ std::move(boundTableFunc.bindData), columnNames, columnTypes);
+ return std::make_unique(ScanSourceType::OBJECT, std::move(info));
+}
+
+} // namespace binder
+} // namespace lbug
diff --git a/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/bind_graph_pattern.cpp b/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/bind_graph_pattern.cpp
new file mode 100644
index 0000000000..1ad1af5ee8
--- /dev/null
+++ b/graph-wasm/lbug-0.12.2/lbug-src/src/binder/bind/bind_graph_pattern.cpp
@@ -0,0 +1,695 @@
+#include "binder/binder.h"
+#include "binder/expression/expression_util.h"
+#include "binder/expression/path_expression.h"
+#include "binder/expression/property_expression.h"
+#include "binder/expression_visitor.h"
+#include "catalog/catalog.h"
+#include "catalog/catalog_entry/node_table_catalog_entry.h"
+#include "catalog/catalog_entry/rel_group_catalog_entry.h"
+#include "common/enums/rel_direction.h"
+#include "common/exception/binder.h"
+#include "common/string_format.h"
+#include "common/utils.h"
+#include "function/cast/functions/cast_from_string_functions.h"
+#include "function/gds/rec_joins.h"
+#include "function/rewrite_function.h"
+#include "function/schema/vector_node_rel_functions.h"
+#include "main/client_context.h"
+#include "transaction/transaction.h"
+
+using namespace lbug::common;
+using namespace lbug::parser;
+using namespace lbug::catalog;
+
+namespace lbug {
+namespace binder {
+
+// A graph pattern contains node/rel and a set of key-value pairs associated with the variable. We
+// bind node/rel as query graph and key-value pairs as a separate collection. This collection is
+// interpreted in two different ways.
+// - In MATCH clause, these are additional predicates to WHERE clause
+// - In UPDATE clause, there are properties to set.
+// We do not store key-value pairs in query graph primarily because we will merge key-value
+// std::pairs with other predicates specified in WHERE clause.
+BoundGraphPattern Binder::bindGraphPattern(const std::vector& graphPattern) {
+ auto queryGraphCollection = QueryGraphCollection();
+ for (auto& patternElement : graphPattern) {
+ queryGraphCollection.addAndMergeQueryGraphIfConnected(bindPatternElement(patternElement));
+ }
+ queryGraphCollection.finalize();
+ auto boundPattern = BoundGraphPattern();
+ boundPattern.queryGraphCollection = std::move(queryGraphCollection);
+ return boundPattern;
+}
+
+// Grammar ensures pattern element is always connected and thus can be bound as a query graph.
+QueryGraph Binder::bindPatternElement(const PatternElement& patternElement) {
+ auto queryGraph = QueryGraph();
+ expression_vector nodeAndRels;
+ auto leftNode = bindQueryNode(*patternElement.getFirstNodePattern(), queryGraph);
+ nodeAndRels.push_back(leftNode);
+ for (auto i = 0u; i < patternElement.getNumPatternElementChains(); ++i) {
+ auto patternElementChain = patternElement.getPatternElementChain(i);
+ auto rightNode = bindQueryNode(*patternElementChain->getNodePattern(), queryGraph);
+ auto rel =
+ bindQueryRel(*patternElementChain->getRelPattern(), leftNode, rightNode, queryGraph);
+ nodeAndRels.push_back(rel);
+ nodeAndRels.push_back(rightNode);
+ leftNode = rightNode;
+ }
+ if (patternElement.hasPathName()) {
+ auto pathName = patternElement.getPathName();
+ auto pathExpression = createPath(pathName, nodeAndRels);
+ addToScope(pathName, pathExpression);
+ }
+ return queryGraph;
+}
+
+static LogicalType getRecursiveRelLogicalType(const LogicalType& nodeType,
+ const LogicalType& relType) {
+ auto nodesType = LogicalType::LIST(nodeType.copy());
+ auto relsType = LogicalType::LIST(relType.copy());
+ std::vector