8000 enhancement (v3.0.0): expose the query cursor in every binding · Issue #462 · project-minigraf/minigraf · GitHub
[go: up one dir, main page]

Skip to content

enhancement (v3.0.0): expose the query cursor in every binding #462

Closed

Description

@adityamukho

Follow-up to #432. PR #461 added the Rust cursor API on v3. This issue adds a matching cursor object to every binding, so 3.0.0 is the last release that binding users have to change for incremental results.

What landed in core (v3 @ 1e4386d)

pub fn Minigraf::query(&self, datalog: &str) -> Result<Cursor, MinigrafError>;
pub fn PreparedQuery::query(&self, binds: &[(&str, BindValue)]) -> Result<Cursor, MinigrafError>;
impl Cursor {
    pub fn vars(&self) -> &[String];
    pub fn next_batch(&mut self, max_rows: usize) -> Result<Option<Batch>, MinigrafError>;
    pub fn close(self);
}
impl Iterator for Cursor { type Item = Result<Vec<Value>, MinigrafError>; }
  • Cursor is owned and Send. It borrows nothing from the handle and can outlive it.
  • The answer is fixed when the cursor opens.
  • A batch is never empty, None means the end, and max_rows == 0 counts as 1.
  • transact, retract and rule give API-012. A query with bind slots gives API-010.
  • For now the engine computes the answer when the cursor opens. Streaming operators replace it in 3.x without changing the API.

Design: docs/superpowers/specs/2026-10-07-cursor-api-design.md.

Binding shape

uniffi cannot carry an iterator, so every binding exposes the same object, with a native iterator on top:

  • db.query(datalog) -> Cursor
  • Cursor.vars() -> list of strings
  • Cursor.next_batch(n) -> rows, or nothing at the end. Rows use the binding's existing result encoding: the JSON rows execute() returns today, or native values where the binding already decodes them.
  • Cursor.close()
  • An idiomatic iterator over next_batch: Python __iter__ and a context manager, a JS async/sync iterator, Java Iterator + AutoCloseable, Swift Sequence, C next/free functions.

The cursor wraps the core Cursor behind a Mutex, because next_batch takes &mut self. After close(), next_batch returns the end or a clear error, never a panic. PreparedQuery.query follows when PreparedQuery is exposed (#181, v3.1.0).

Checklist

  • minigraf-python (Tier 1, uniffi)
  • minigraf-java (uniffi)
  • minigraf-android (uniffi)
  • minigraf-swift (uniffi)
  • minigraf-node (napi)
  • minigraf-wasm (wasm-bindgen / WASI)
  • minigraf-c (cbindgen: minigraf_query, minigraf_cursor_next_batch, minigraf_cursor_free)
  • BrowserDb in core (src/browser/, browser feature)
  • minigraf-binding-template updated so new bindings start with it

Acceptance

Part of #432.

Done (2026-10-07)

All on each repo's v3 branch, pinned to core v3 by git revision:
minigraf-python#6, minigraf-java#9, minigraf-android#6, minigraf-swift#6, minigraf-node#5, minigraf-c#4, minigraf-wasm#4, minigraf-binding-template#1. BrowserDb.query → BrowserCursor in core #468. Every binding tests batches of 1/7/1000 against execute(), close() after the first batch, a cursor fixed at open, and API-012. The WASI package ships only the REPL binary, so it has no cursor to expose. The shared shape is in docs/superpowers/specs/2026-10-07-binding-etl-surface-design.md.

The last acceptance item (Python batch-1,000 round trip in the #394 envelope) moves to #394. #432's acceptance list has it too.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestrustPull requests that update rust code

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      0