8000 feat: user functions, minigraf_register_predicate/_aggregate (minigraf#180 PR 8) by adityamukho · Pull Request #13 · project-minigraf/minigraf-c · GitHub
[go: up one dir, main page]

Skip to content

feat: user functions, minigraf_register_predicate/_aggregate (minigraf#180 PR 8) - #13

Merged
adityamukho merged 1 commit into
v3from
feat/180-udf-ffi
Oct 25, 2026
Merged

adityamukho merged 1 commit into
v3from
feat/180-udf-ffi

Conversation

@adityamukho
Copy link
Copy Markdown
Contributor

Plan step 23 of minigraf's docs/superpowers/plans/2026-10-24-180-udf-uniffi.md (spec §6.1, §6.2). Part of project-minigraf/minigraf#180, which is closed by hand after the Node PR.

What

  • Pin minigraf v3 7c8c5540 → 02243f16 (#757), the pin the other bindings use. The old pin already had #181 and #316. #306 (:limit/:order-by) needs no binding change.
  • Handle locks (spec §6.1): every minigraf_* call clones the Minigraf under handle.db.lock() and releases the lock before calling the core (MiniGrafDb::core()). Before this, a callback that called back into its own database deadlocked: the re-entry test hangs with the old lock in minigraf_execute.
  • User functions (spec §6.2), with the header regenerated by cbindgen 0.29.2:
    • minigraf_register_predicate(db, name, f, user_data, destroy), minigraf_register_aggregate(db, name, init, step, finish, acc_free, user_data, destroy), over try_register_predicate/try_register_aggregate.
    • minigraf_udf_error(ctx, msg), minigraf_udf_result(ctx, value_json). MiniGrafUdfContext is a stack value with result/error slots, valid for one call.
    • Values cross in the fact records' typed JSON, {"type": "integer", "value": 30}.
    • UserData (user_data + destroy) is unsafe impl Send + Sync, and its Drop calls destroy. Accumulator's Drop calls acc_free. The header states the threading contract.
  • Consumer contract: one predicate (adult?), one aggregate (product, acc_free = free), a failing call → QRY-014, and both destroys run at close.
  • README: User functions section (example, values, failures, lifecycle, threading, cost, re-entry).

Deviations from the spec text

  • A call that sets minigraf_udf_error fails, whatever it returns. The spec only says -1 means failed "after minigraf_udf_error". This way an error a callback sets is never dropped.
  • A predicate return code other than 1/0/-1, or a step/finish code other than 0/-1, fails the call (user function returned N). The spec does not say what such a code means. Reading it as true would be a silent guess.
  • A failed registration runs destroy before it returns (NULL argument → API-017, a name already taken → INT-040, NULL db), as in SQLite's sqlite3_create_function_v2. The spec does not cover this case.
  • minigraf_udf_error(ctx, NULL) sets user function failed. minigraf_udf_result(ctx, NULL) clears the result, so finish then fails with finish set no result.
  • A callback must not call minigraf_close on the database it runs on (stated in the header and README).

Tests

9 new Rust tests drive the C ABI through extern "C" callbacks (28 total):

  • predicate true/false
  • -1 with and without a message
  • an error set alongside a return of 1
  • a return of 2
  • an aggregate in :find and :over, with the count passed through
  • the six aggregate failure paths: init NULL, step -1, no result, bad JSON, a non-UUID ref, silent -1
  • destroy/acc_free counts, including a cursor freed before its first batch and destroy waiting for the last cursor after minigraf_close
  • destroy on a failed registration
  • re-entry: a predicate runs minigraf_query + minigraf_execute, writes, and registers a predicate on its own database
  • NULL context arguments

Checked locally and on the bench machine:

  • cargo test, cargo clippy --all-targets
  • the consumer contract and etl_smoke.c built with gcc -std=c99 -Wall -Wextra -Werror
  • the lib tests under AddressSanitizer: no errors. The only leaks are the 10 test Counts that the tests leak on purpose with Box::leak.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Urda7FD9CYYoxdeGGJa11x

< 8000 !-- '"` -->

…f#180)

C user functions per minigraf's UDF spec §6.2, SQLite style: a function
pointer, user_data and an optional destroy; results and errors go through a
MiniGrafUdfContext. A failing call stops the query with QRY-014.

- minigraf_register_predicate, minigraf_register_aggregate (init/step/finish
  + acc_free), minigraf_udf_error, minigraf_udf_result over the core's
  try_register_*. Values cross as the fact records' typed JSON.
- destroy runs when the last reference drops (after close and every cursor),
  and on a failed registration. acc_free runs when the query drops the
  accumulator, including an early minigraf_cursor_free.
- Handle locks: every minigraf_* call clones the Minigraf under the handle
  mutex and releases it before calling the core, so a callback can call back
  into its own database (it deadlocked before).
- Pin minigraf v3 at 02243f16 (#757); the old pin had #181/#316 already.
- Consumer contract: one predicate, one aggregate and a QRY-014 failure.
- README: User functions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Urda7FD9CYYoxdeGGJa11x
@adityamukho
adityamukho merged commit 22e4a85 into v3 Oct 25, 2026
15 checks passed
@adityamukho
adityamukho deleted the feat/180-udf-ffi branch October 25, 2026 08:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

0